From 7d44b48c4b978f9308c16850c41b886b12e74265 Mon Sep 17 00:00:00 2001 From: Martin Lippert Date: Fri, 28 Jun 2024 08:58:27 +0200 Subject: [PATCH] GH-1261: extracted general annotation attribute content-assist mechanics, first steps towards a more re-usaeble implementation --- .../BootJavaCompletionEngineConfigurer.java | 11 +- ...notationAttributeCompletionProcessor.java} | 112 ++--- ...nnotationAttributeCompletionProvider.java} | 17 +- .../beans/DependsOnCompletionProcessor.java | 441 +++++++++--------- .../beans/QualifierCompletionProvider.java | 68 +++ .../java/scope/ScopeCompletionProcessor.java | 136 +++--- .../editor/harness/PropertyIndexHarness.java | 6 +- .../test/DependsOnCompletionProviderTest.java | 20 +- .../test/QualifierCompletionProviderTest.java | 2 +- .../java/scope/test/ScopeCompletionTest.java | 47 +- .../java/value/test/ValueCompletionTest.java | 10 +- 11 files changed, 463 insertions(+), 407 deletions(-) rename headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/{beans/QualifierCompletionProcessor.java => annotations/AnnotationAttributeCompletionProcessor.java} (70%) rename headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/{requestmapping/RequestMappingCompletionProcessor.java => annotations/AnnotationAttributeCompletionProvider.java} (55%) create mode 100644 headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/QualifierCompletionProvider.java diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootJavaCompletionEngineConfigurer.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootJavaCompletionEngineConfigurer.java index 229b4b9db..a24195bdc 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootJavaCompletionEngineConfigurer.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootJavaCompletionEngineConfigurer.java @@ -23,9 +23,10 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex; import org.springframework.ide.vscode.boot.java.Annotations; +import org.springframework.ide.vscode.boot.java.annotations.AnnotationAttributeCompletionProcessor; import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies; import org.springframework.ide.vscode.boot.java.beans.DependsOnCompletionProcessor; -import org.springframework.ide.vscode.boot.java.beans.QualifierCompletionProcessor; +import org.springframework.ide.vscode.boot.java.beans.QualifierCompletionProvider; import org.springframework.ide.vscode.boot.java.data.DataRepositoryCompletionProcessor; import org.springframework.ide.vscode.boot.java.handlers.BootJavaCompletionEngine; import org.springframework.ide.vscode.boot.java.handlers.CompletionProvider; @@ -111,12 +112,14 @@ public class BootJavaCompletionEngineConfigurer { Map providers = new HashMap<>(); - providers.put(Annotations.SCOPE, new ScopeCompletionProcessor()); providers.put(Annotations.VALUE, new ValueCompletionProcessor(javaProjectFinder, indexProvider, adHocProperties)); - providers.put(Annotations.DEPENDS_ON, new DependsOnCompletionProcessor(javaProjectFinder, springIndex)); - providers.put(Annotations.QUALIFIER, new QualifierCompletionProcessor(javaProjectFinder, springIndex)); providers.put(Annotations.REPOSITORY, new DataRepositoryCompletionProcessor()); + + providers.put(Annotations.SCOPE, new AnnotationAttributeCompletionProcessor(javaProjectFinder, Map.of("value", new ScopeCompletionProcessor()))); + providers.put(Annotations.DEPENDS_ON, new AnnotationAttributeCompletionProcessor(javaProjectFinder, Map.of("value", new DependsOnCompletionProcessor(springIndex)))); + providers.put(Annotations.QUALIFIER, new AnnotationAttributeCompletionProcessor(javaProjectFinder, Map.of("value", new QualifierCompletionProvider(springIndex)))); + return new BootJavaCompletionEngine(cuCache, providers, snippetManager); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/QualifierCompletionProcessor.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/annotations/AnnotationAttributeCompletionProcessor.java similarity index 70% rename from headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/QualifierCompletionProcessor.java rename to headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/annotations/AnnotationAttributeCompletionProcessor.java index 936880fc5..2a77afa39 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/QualifierCompletionProcessor.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/annotations/AnnotationAttributeCompletionProcessor.java @@ -8,18 +8,16 @@ * Contributors: * Broadcom - initial API and implementation *******************************************************************************/ -package org.springframework.ide.vscode.boot.java.beans; +package org.springframework.ide.vscode.boot.java.annotations; -import java.util.Arrays; import java.util.Collection; import java.util.HashSet; -import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; -import java.util.stream.Stream; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.Annotation; @@ -28,28 +26,26 @@ import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.MemberValuePair; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.StringLiteral; -import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex; -import org.springframework.ide.vscode.boot.java.Annotations; +import org.springframework.ide.vscode.boot.java.beans.QualifierCompletionProposal; import org.springframework.ide.vscode.boot.java.handlers.CompletionProvider; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits; import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; -import org.springframework.ide.vscode.commons.protocol.spring.Bean; import org.springframework.ide.vscode.commons.util.BadLocationException; import org.springframework.ide.vscode.commons.util.text.TextDocument; /** * @author Martin Lippert */ -public class QualifierCompletionProcessor implements CompletionProvider { - +public class AnnotationAttributeCompletionProcessor implements CompletionProvider { + private final JavaProjectFinder projectFinder; - private final SpringMetamodelIndex springIndex; + private final Map completionProviders; - public QualifierCompletionProcessor(JavaProjectFinder projectFinder, SpringMetamodelIndex springIndex) { + public AnnotationAttributeCompletionProcessor(JavaProjectFinder projectFinder, Map completionProviders) { this.projectFinder = projectFinder; - this.springIndex = springIndex; + this.completionProviders = completionProviders; } @Override @@ -67,7 +63,6 @@ public class QualifierCompletionProcessor implements CompletionProvider { // case: @Qualifier(<*>) if (node == annotation && doc.get(offset - 1, 2).endsWith("()")) { createCompletionProposals(project, doc, node, completions, offset, offset, "", (beanName) -> "\"" + beanName + "\""); - } // case: @Qualifier(prefix<*>) else if (node instanceof SimpleName && node.getParent() instanceof Annotation) { @@ -105,7 +100,41 @@ public class QualifierCompletionProcessor implements CompletionProvider { e.printStackTrace(); } } + + /** + * create the concrete completion proposal + */ + private void createCompletionProposals(IJavaProject project, TextDocument doc, ASTNode node, Collection completions, int startOffset, int endOffset, + String filterPrefix, Function createReplacementText) { + Set alreadyMentionedValues = alreadyMentionedValues(node); + + AnnotationAttributeCompletionProvider completionProvider = this.completionProviders.get("value"); + if (completionProvider != null) { + List candidates = completionProvider.getCompletionCandidates(project); + + List filteredCandidates = candidates.stream() + .filter(candidate -> candidate.toLowerCase().startsWith(filterPrefix.toLowerCase())) + .filter(candidate -> !alreadyMentionedValues.contains(candidate)) + .collect(Collectors.toList()); + + double score = filteredCandidates.size(); + for (String candidate : filteredCandidates) { + + DocumentEdits edits = new DocumentEdits(doc, false); + edits.replace(startOffset, endOffset, createReplacementText.apply(candidate)); + + QualifierCompletionProposal proposal = new QualifierCompletionProposal(edits, candidate, candidate, null, score--); + completions.add(proposal); + } + } + } + + + // + // internal computation of the right positions, prefixes, etc. + // + private void computeProposalsForSimpleName(IJavaProject project, ASTNode node, Collection completions, int offset, TextDocument doc) { String prefix = identifyPropertyPrefix(node.toString(), offset - node.getStartPosition()); @@ -123,7 +152,7 @@ public class QualifierCompletionProcessor implements CompletionProvider { String prefix = identifyPropertyPrefix(doc.get(node.getStartPosition() + 1, length), length); int startOffset = offset - prefix.length(); - int endOffset = offset; + int endOffset = node.getStartPosition() + node.getLength() - 1; createCompletionProposals(project, doc, node, completions, startOffset, endOffset, prefix, (beanName) -> beanName); } @@ -142,34 +171,6 @@ public class QualifierCompletionProcessor implements CompletionProvider { } } - private void createCompletionProposals(IJavaProject project, TextDocument doc, ASTNode node, Collection completions, int startOffset, int endOffset, - String filterPrefix, Function createReplacementText) { - - Set mentionedQualifiers = alreadyMentionedValues(node); - - Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName()); - Set candidates = Stream.concat( - findAllQualifiers(beans), - Arrays.stream(beans).map(bean -> bean.getName())) - .collect(Collectors.toCollection(LinkedHashSet::new)); - - List filteredCandidates = candidates.stream() - .filter(candidate -> candidate.toLowerCase().startsWith(filterPrefix.toLowerCase())) - .filter(candidate -> !mentionedQualifiers.contains(candidate)) - .collect(Collectors.toList()); - - - double score = filteredCandidates.size(); - for (String candidate : filteredCandidates) { - - DocumentEdits edits = new DocumentEdits(doc, false); - edits.replace(startOffset, endOffset, createReplacementText.apply(candidate)); - - QualifierCompletionProposal proposal = new QualifierCompletionProposal(edits, candidate, candidate, null, score--); - completions.add(proposal); - } - } - private String identifyPropertyPrefix(String nodeContent, int offset) { String result = nodeContent.substring(0, offset); @@ -186,7 +187,7 @@ public class QualifierCompletionProcessor implements CompletionProvider { return result; } - private Set alreadyMentionedValues(ASTNode node) { + protected Set alreadyMentionedValues(ASTNode node) { Set result = new HashSet<>(); ArrayInitializer arrayNode = null; @@ -213,29 +214,4 @@ public class QualifierCompletionProcessor implements CompletionProvider { return result; } - private Stream findAllQualifiers(Bean[] beans) { - - Stream qualifiersFromBeans = Arrays.stream(beans) - // annotations from beans themselves - .flatMap(bean -> Arrays.stream(bean.getAnnotations())) - .filter(annotation -> Annotations.QUALIFIER.equals(annotation.getAnnotationType())) - .filter(annotation -> annotation.getAttributes() != null && annotation.getAttributes().containsKey("value") && annotation.getAttributes().get("value").length == 1) - .map(annotation -> annotation.getAttributes().get("value")[0]); - - Stream qualifiersFromInjectionPoints = Arrays.stream(beans) - // annotations from beans themselves - .filter(bean -> bean.getInjectionPoints() != null) - .flatMap(bean -> Arrays.stream(bean.getInjectionPoints())) - .filter(injectionPoint -> injectionPoint.getAnnotations() != null) - .flatMap(injectionPoint -> Arrays.stream(injectionPoint.getAnnotations())) - .filter(annotation -> Annotations.QUALIFIER.equals(annotation.getAnnotationType())) - .filter(annotation -> annotation.getAttributes() != null && annotation.getAttributes().containsKey("value") && annotation.getAttributes().get("value").length == 1) - .map(annotation -> annotation.getAttributes().get("value")[0]); - - return Stream.concat(qualifiersFromBeans, qualifiersFromInjectionPoints); - - } - - - } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingCompletionProcessor.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/annotations/AnnotationAttributeCompletionProvider.java similarity index 55% rename from headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingCompletionProcessor.java rename to headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/annotations/AnnotationAttributeCompletionProvider.java index 219ee6fe2..2f96e6c69 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingCompletionProcessor.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/annotations/AnnotationAttributeCompletionProvider.java @@ -1,18 +1,21 @@ /******************************************************************************* - * Copyright (c) 2017 Pivotal, Inc. + * Copyright (c) 2024 Broadcom * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-v10.html * * Contributors: - * Pivotal, Inc. - initial API and implementation + * Broadcom - initial API and implementation *******************************************************************************/ -package org.springframework.ide.vscode.boot.java.requestmapping; +package org.springframework.ide.vscode.boot.java.annotations; -/** - * @author Martin Lippert - */ -public class RequestMappingCompletionProcessor { +import java.util.List; + +import org.springframework.ide.vscode.commons.java.IJavaProject; + +public interface AnnotationAttributeCompletionProvider { + + List getCompletionCandidates(IJavaProject project); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/DependsOnCompletionProcessor.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/DependsOnCompletionProcessor.java index de954fb25..10a4539a1 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/DependsOnCompletionProcessor.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/DependsOnCompletionProcessor.java @@ -11,248 +11,235 @@ package org.springframework.ide.vscode.boot.java.beans; import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -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.ITypeBinding; -import org.eclipse.jdt.core.dom.MemberValuePair; -import org.eclipse.jdt.core.dom.SimpleName; -import org.eclipse.jdt.core.dom.StringLiteral; import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex; -import org.springframework.ide.vscode.boot.java.handlers.CompletionProvider; +import org.springframework.ide.vscode.boot.java.annotations.AnnotationAttributeCompletionProvider; import org.springframework.ide.vscode.commons.java.IJavaProject; -import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits; -import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal; -import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; -import org.springframework.ide.vscode.commons.protocol.spring.Bean; -import org.springframework.ide.vscode.commons.util.BadLocationException; -import org.springframework.ide.vscode.commons.util.text.IDocument; -import org.springframework.ide.vscode.commons.util.text.TextDocument; /** * @author Martin Lippert */ -public class DependsOnCompletionProcessor implements CompletionProvider { +public class DependsOnCompletionProcessor implements AnnotationAttributeCompletionProvider { - private final JavaProjectFinder projectFinder; private final SpringMetamodelIndex springIndex; - public DependsOnCompletionProcessor(JavaProjectFinder projectFinder, SpringMetamodelIndex springIndex) { - this.projectFinder = projectFinder; + public DependsOnCompletionProcessor(SpringMetamodelIndex springIndex) { this.springIndex = springIndex; } +// @Override +// public void provideCompletions(ASTNode node, Annotation annotation, ITypeBinding type, int offset, TextDocument doc, Collection completions) { +// +// Optional optionalProject = projectFinder.find(doc.getId()); +// if (!optionalProject.isPresent()) { +// return; +// } +// +// IJavaProject project = optionalProject.get(); +// +// try { +// +// // case: @DependsOn(<*>) +// if (node == annotation && doc.get(offset - 1, 2).endsWith("()")) { +// Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName()); +// +// for (Bean bean : beans) { +// +// DocumentEdits edits = new DocumentEdits(doc, false); +// edits.replace(offset, offset, "\"" + bean.getName() + "\""); +// +// DependsOnCompletionProposal proposal = new DependsOnCompletionProposal(edits, bean.getName(), bean.getName(), null); +// +// completions.add(proposal); +// } +// } +// // case: @DependsOn(prefix<*>) +// else if (node instanceof SimpleName && node.getParent() instanceof Annotation) { +// computeProposalsForSimpleName(project, node, completions, offset, doc); +// } +// // case: @DependsOn(value=<*>) +// else if (node instanceof SimpleName && node.getParent() instanceof MemberValuePair +// && "value".equals(((MemberValuePair)node.getParent()).getName().toString())) { +// computeProposalsForSimpleName(project, node, completions, offset, doc); +// } +// // case: @DependsOn("prefix<*>") +// else if (node instanceof StringLiteral && node.getParent() instanceof Annotation) { +// if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) { +// computeProposalsForStringLiteral(project, node, completions, offset, doc); +// } +// } +// else if (node instanceof StringLiteral && node.getParent() instanceof ArrayInitializer) { +// if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) { +// computeProposalsForInsideArrayInitializer(project, node, completions, offset, doc); +// } +// } +// // case: @DependsOn(value="prefix<*>") +// else if (node instanceof StringLiteral && node.getParent() instanceof MemberValuePair +// && "value".equals(((MemberValuePair)node.getParent()).getName().toString())) { +// if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) { +// computeProposalsForStringLiteral(project, node, completions, offset, doc); +// } +// } +// // case: @DependsOn({<*>}) +// else if (node instanceof ArrayInitializer && node.getParent() instanceof Annotation) { +// computeProposalsForArrayInitializr(project, (ArrayInitializer) node, completions, offset, doc); +// } +// } +// catch (Exception e) { +// e.printStackTrace(); +// } +// } +// +// private void computeProposalsForSimpleName(IJavaProject project, ASTNode node, Collection completions, int offset, IDocument doc) { +// String prefix = identifyPropertyPrefix(node.toString(), offset - node.getStartPosition()); +// +// int startOffset = node.getStartPosition(); +// int endOffset = node.getStartPosition() + node.getLength(); +// +// String proposalPrefix = "\""; +// String proposalPostfix = "\""; +// +// Set mentionedBeans = alreadyMentionedBeans(node); +// +// Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName()); +// List matchingBeans = Arrays.stream(beans) +// .filter(bean -> bean.getName().toLowerCase().startsWith(prefix.toLowerCase())) +// .filter(bean -> !mentionedBeans.contains(bean.getName())) +// .collect(Collectors.toList()); +// +// for (Bean bean : matchingBeans) { +// +// DocumentEdits edits = new DocumentEdits(doc, false); +// edits.replace(startOffset, endOffset, proposalPrefix + bean.getName() + proposalPostfix); +// +// DependsOnCompletionProposal proposal = new DependsOnCompletionProposal(edits, bean.getName(), bean.getName(), null); +// +// completions.add(proposal); +// } +// } +// +// private void computeProposalsForStringLiteral(IJavaProject project, ASTNode node, Collection completions, int offset, IDocument doc) throws BadLocationException { +// int length = offset - (node.getStartPosition() + 1); +// +// String prefix = identifyPropertyPrefix(doc.get(node.getStartPosition() + 1, length), length); +// int startOffset = offset - prefix.length(); +// int endOffset = offset; +// +// Set mentionedBeans = alreadyMentionedBeans(node); +// +// Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName()); +// +// final String filterPrefix = prefix; +// List matchingBeans = Arrays.stream(beans) +// .filter(bean -> bean.getName().toLowerCase().startsWith(filterPrefix.toLowerCase())) +// .filter(bean -> !mentionedBeans.contains(bean.getName())) +// .collect(Collectors.toList()); +// +// for (Bean bean : matchingBeans) { +// +// DocumentEdits edits = new DocumentEdits(doc, false); +// edits.replace(startOffset, endOffset, bean.getName()); +// +// DependsOnCompletionProposal proposal = new DependsOnCompletionProposal(edits, bean.getName(), bean.getName(), null); +// +// completions.add(proposal); +// } +// } +// +// private void computeProposalsForArrayInitializr(IJavaProject project, ArrayInitializer node, Collection completions, int offset, IDocument doc) { +// Set mentionedBeans = alreadyMentionedBeans(node); +// +// Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName()); +// List filteredBeans = Arrays.stream(beans) +// .filter(bean -> !mentionedBeans.contains(bean.getName())) +// .collect(Collectors.toList()); +// +// for (Bean bean : filteredBeans) { +// +// DocumentEdits edits = new DocumentEdits(doc, false); +// edits.replace(offset, offset, "\"" + bean.getName() + "\""); +// +// DependsOnCompletionProposal proposal = new DependsOnCompletionProposal(edits, bean.getName(), bean.getName(), null); +// +// completions.add(proposal); +// } +// } +// +// private void computeProposalsForInsideArrayInitializer(IJavaProject project, ASTNode node, Collection completions, int offset, TextDocument doc) throws BadLocationException { +// int length = offset - (node.getStartPosition() + 1); +// if (length >= 0) { +// computeProposalsForStringLiteral(project, node, completions, offset, doc); +// } +// else { +// Set mentionedBeans = alreadyMentionedBeans(node); +// +// Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName()); +// List filteredBeans = Arrays.stream(beans) +// .filter(bean -> !mentionedBeans.contains(bean.getName())) +// .collect(Collectors.toList()); +// +// for (Bean bean : filteredBeans) { +// +// DocumentEdits edits = new DocumentEdits(doc, false); +// edits.replace(offset, offset, "\"" + bean.getName() + "\","); +// +// DependsOnCompletionProposal proposal = new DependsOnCompletionProposal(edits, bean.getName(), bean.getName(), null); +// +// completions.add(proposal); +// } +// } +// } +// +// private String identifyPropertyPrefix(String nodeContent, int offset) { +// String result = nodeContent.substring(0, offset); +// +// int i = offset - 1; +// while (i >= 0) { +// char c = nodeContent.charAt(i); +// if (c == '}' || c == '{' || c == '$' || c == '#') { +// result = result.substring(i + 1, offset); +// break; +// } +// i--; +// } +// +// return result; +// } +// +// private Set alreadyMentionedBeans(ASTNode node) { +// Set result = new HashSet<>(); +// +// ArrayInitializer arrayNode = null; +// while (node != null && arrayNode == null && !(node instanceof Annotation)) { +// if (node instanceof ArrayInitializer) { +// arrayNode = (ArrayInitializer) node; +// } +// else { +// node = node.getParent(); +// } +// } +// +// if (arrayNode != null) { +// List expressions = arrayNode.expressions(); +// for (Object expression : expressions) { +// if (expression instanceof StringLiteral) { +// StringLiteral stringExr = (StringLiteral) expression; +// String value = stringExr.getLiteralValue(); +// result.add(value); +// } +// } +// } +// +// return result; +// } + @Override - public void provideCompletions(ASTNode node, Annotation annotation, ITypeBinding type, int offset, TextDocument doc, Collection completions) { - - Optional optionalProject = projectFinder.find(doc.getId()); - if (!optionalProject.isPresent()) { - return; - } - - IJavaProject project = optionalProject.get(); - - try { - - // case: @DependsOn(<*>) - if (node == annotation && doc.get(offset - 1, 2).endsWith("()")) { - Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName()); - - for (Bean bean : beans) { - - DocumentEdits edits = new DocumentEdits(doc, false); - edits.replace(offset, offset, "\"" + bean.getName() + "\""); - - DependsOnCompletionProposal proposal = new DependsOnCompletionProposal(edits, bean.getName(), bean.getName(), null); - - completions.add(proposal); - } - } - // case: @DependsOn(prefix<*>) - else if (node instanceof SimpleName && node.getParent() instanceof Annotation) { - computeProposalsForSimpleName(project, node, completions, offset, doc); - } - // case: @DependsOn(value=<*>) - else if (node instanceof SimpleName && node.getParent() instanceof MemberValuePair - && "value".equals(((MemberValuePair)node.getParent()).getName().toString())) { - computeProposalsForSimpleName(project, node, completions, offset, doc); - } - // case: @DependsOn("prefix<*>") - else if (node instanceof StringLiteral && node.getParent() instanceof Annotation) { - if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) { - computeProposalsForStringLiteral(project, node, completions, offset, doc); - } - } - else if (node instanceof StringLiteral && node.getParent() instanceof ArrayInitializer) { - if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) { - computeProposalsForInsideArrayInitializer(project, node, completions, offset, doc); - } - } - // case: @DependsOn(value="prefix<*>") - else if (node instanceof StringLiteral && node.getParent() instanceof MemberValuePair - && "value".equals(((MemberValuePair)node.getParent()).getName().toString())) { - if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) { - computeProposalsForStringLiteral(project, node, completions, offset, doc); - } - } - // case: @DependsOn({<*>}) - else if (node instanceof ArrayInitializer && node.getParent() instanceof Annotation) { - computeProposalsForArrayInitializr(project, (ArrayInitializer) node, completions, offset, doc); - } - } - catch (Exception e) { - e.printStackTrace(); - } - } - - private void computeProposalsForSimpleName(IJavaProject project, ASTNode node, Collection completions, int offset, IDocument doc) { - String prefix = identifyPropertyPrefix(node.toString(), offset - node.getStartPosition()); - - int startOffset = node.getStartPosition(); - int endOffset = node.getStartPosition() + node.getLength(); - - String proposalPrefix = "\""; - String proposalPostfix = "\""; - - Set mentionedBeans = alreadyMentionedBeans(node); - - Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName()); - List matchingBeans = Arrays.stream(beans) - .filter(bean -> bean.getName().toLowerCase().startsWith(prefix.toLowerCase())) - .filter(bean -> !mentionedBeans.contains(bean.getName())) - .collect(Collectors.toList()); - - for (Bean bean : matchingBeans) { - - DocumentEdits edits = new DocumentEdits(doc, false); - edits.replace(startOffset, endOffset, proposalPrefix + bean.getName() + proposalPostfix); - - DependsOnCompletionProposal proposal = new DependsOnCompletionProposal(edits, bean.getName(), bean.getName(), null); - - completions.add(proposal); - } - } - - private void computeProposalsForStringLiteral(IJavaProject project, ASTNode node, Collection completions, int offset, IDocument doc) throws BadLocationException { - int length = offset - (node.getStartPosition() + 1); - - String prefix = identifyPropertyPrefix(doc.get(node.getStartPosition() + 1, length), length); - int startOffset = offset - prefix.length(); - int endOffset = offset; - - Set mentionedBeans = alreadyMentionedBeans(node); - - Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName()); - - final String filterPrefix = prefix; - List matchingBeans = Arrays.stream(beans) - .filter(bean -> bean.getName().toLowerCase().startsWith(filterPrefix.toLowerCase())) - .filter(bean -> !mentionedBeans.contains(bean.getName())) - .collect(Collectors.toList()); - - for (Bean bean : matchingBeans) { - - DocumentEdits edits = new DocumentEdits(doc, false); - edits.replace(startOffset, endOffset, bean.getName()); - - DependsOnCompletionProposal proposal = new DependsOnCompletionProposal(edits, bean.getName(), bean.getName(), null); - - completions.add(proposal); - } - } - - private void computeProposalsForArrayInitializr(IJavaProject project, ArrayInitializer node, Collection completions, int offset, IDocument doc) { - Set mentionedBeans = alreadyMentionedBeans(node); - - Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName()); - List filteredBeans = Arrays.stream(beans) - .filter(bean -> !mentionedBeans.contains(bean.getName())) - .collect(Collectors.toList()); - - for (Bean bean : filteredBeans) { - - DocumentEdits edits = new DocumentEdits(doc, false); - edits.replace(offset, offset, "\"" + bean.getName() + "\""); - - DependsOnCompletionProposal proposal = new DependsOnCompletionProposal(edits, bean.getName(), bean.getName(), null); - - completions.add(proposal); - } - } - - private void computeProposalsForInsideArrayInitializer(IJavaProject project, ASTNode node, Collection completions, int offset, TextDocument doc) throws BadLocationException { - int length = offset - (node.getStartPosition() + 1); - if (length >= 0) { - computeProposalsForStringLiteral(project, node, completions, offset, doc); - } - else { - Set mentionedBeans = alreadyMentionedBeans(node); - - Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName()); - List filteredBeans = Arrays.stream(beans) - .filter(bean -> !mentionedBeans.contains(bean.getName())) - .collect(Collectors.toList()); - - for (Bean bean : filteredBeans) { - - DocumentEdits edits = new DocumentEdits(doc, false); - edits.replace(offset, offset, "\"" + bean.getName() + "\","); - - DependsOnCompletionProposal proposal = new DependsOnCompletionProposal(edits, bean.getName(), bean.getName(), null); - - completions.add(proposal); - } - } - } - - private String identifyPropertyPrefix(String nodeContent, int offset) { - String result = nodeContent.substring(0, offset); - - int i = offset - 1; - while (i >= 0) { - char c = nodeContent.charAt(i); - if (c == '}' || c == '{' || c == '$' || c == '#') { - result = result.substring(i + 1, offset); - break; - } - i--; - } - - return result; - } - - private Set alreadyMentionedBeans(ASTNode node) { - Set result = new HashSet<>(); - - ArrayInitializer arrayNode = null; - while (node != null && arrayNode == null && !(node instanceof Annotation)) { - if (node instanceof ArrayInitializer) { - arrayNode = (ArrayInitializer) node; - } - else { - node = node.getParent(); - } - } - - if (arrayNode != null) { - List expressions = arrayNode.expressions(); - for (Object expression : expressions) { - if (expression instanceof StringLiteral) { - StringLiteral stringExr = (StringLiteral) expression; - String value = stringExr.getLiteralValue(); - result.add(value); - } - } - } - - return result; + public List getCompletionCandidates(IJavaProject project) { + return Arrays.stream(this.springIndex.getBeansOfProject(project.getElementName())) + .map(bean -> bean.getName()) + .distinct() + .toList(); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/QualifierCompletionProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/QualifierCompletionProvider.java new file mode 100644 index 000000000..edd49d7ec --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/QualifierCompletionProvider.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2024 Broadcom + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Broadcom - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.beans; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex; +import org.springframework.ide.vscode.boot.java.Annotations; +import org.springframework.ide.vscode.boot.java.annotations.AnnotationAttributeCompletionProvider; +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.protocol.spring.Bean; + +/** + * @author Martin Lippert + */ +public class QualifierCompletionProvider implements AnnotationAttributeCompletionProvider { + + private final SpringMetamodelIndex springIndex; + + public QualifierCompletionProvider(SpringMetamodelIndex springIndex) { + this.springIndex = springIndex; + } + + @Override + public List getCompletionCandidates(IJavaProject project) { + + Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName()); + + return Stream.concat( + findAllQualifiers(beans), + Arrays.stream(beans).map(bean -> bean.getName())) + .distinct() + .toList(); + } + + private Stream findAllQualifiers(Bean[] beans) { + + Stream qualifiersFromBeans = Arrays.stream(beans) + // annotations from beans themselves + .flatMap(bean -> Arrays.stream(bean.getAnnotations())) + .filter(annotation -> Annotations.QUALIFIER.equals(annotation.getAnnotationType())) + .filter(annotation -> annotation.getAttributes() != null && annotation.getAttributes().containsKey("value") && annotation.getAttributes().get("value").length == 1) + .map(annotation -> annotation.getAttributes().get("value")[0]); + + Stream qualifiersFromInjectionPoints = Arrays.stream(beans) + // annotations from beans themselves + .filter(bean -> bean.getInjectionPoints() != null) + .flatMap(bean -> Arrays.stream(bean.getInjectionPoints())) + .filter(injectionPoint -> injectionPoint.getAnnotations() != null) + .flatMap(injectionPoint -> Arrays.stream(injectionPoint.getAnnotations())) + .filter(annotation -> Annotations.QUALIFIER.equals(annotation.getAnnotationType())) + .filter(annotation -> annotation.getAttributes() != null && annotation.getAttributes().containsKey("value") && annotation.getAttributes().get("value").length == 1) + .map(annotation -> annotation.getAttributes().get("value")[0]); + + return Stream.concat(qualifiersFromBeans, qualifiersFromInjectionPoints); + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/scope/ScopeCompletionProcessor.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/scope/ScopeCompletionProcessor.java index a1cc45e60..fe51b8090 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/scope/ScopeCompletionProcessor.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/scope/ScopeCompletionProcessor.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017 Pivotal, Inc. + * Copyright (c) 2017, 2024 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -10,76 +10,84 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.scope; -import java.util.Collection; +import java.util.List; -import org.eclipse.jdt.core.dom.ASTNode; -import org.eclipse.jdt.core.dom.Annotation; -import org.eclipse.jdt.core.dom.ITypeBinding; -import org.eclipse.jdt.core.dom.MemberValuePair; -import org.eclipse.jdt.core.dom.SimpleName; -import org.eclipse.jdt.core.dom.StringLiteral; -import org.springframework.ide.vscode.boot.java.handlers.CompletionProvider; -import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal; -import org.springframework.ide.vscode.commons.util.text.TextDocument; +import org.springframework.ide.vscode.boot.java.annotations.AnnotationAttributeCompletionProvider; +import org.springframework.ide.vscode.commons.java.IJavaProject; /** * @author Martin Lippert */ -public class ScopeCompletionProcessor implements CompletionProvider { +public class ScopeCompletionProcessor implements AnnotationAttributeCompletionProvider { + + private static final List SCOPE_COMPLETIONS = List.of( + "application", + "globalSession", + "prototype", + "request", + "session", + "singleton", + "websocket" + ); @Override - public void provideCompletions(ASTNode node, Annotation annotation, ITypeBinding type, - int offset, TextDocument doc, Collection completions) { - - try { - if (node instanceof SimpleName && node.getParent() instanceof MemberValuePair) { - MemberValuePair memberPair = (MemberValuePair) node.getParent(); - - // case: @Scope(value=<*>) - if ("value".equals(memberPair.getName().toString()) && memberPair.getValue().toString().equals("$missing$")) { - for (ScopeNameCompletion completion : ScopeNameCompletionProposal.COMPLETIONS) { - ICompletionProposal proposal = new ScopeNameCompletionProposal(completion, doc, offset, offset, ""); - completions.add(proposal); - } - } - } - // case: @Scope(<*>) - else if (node == annotation && doc.get(offset - 1, 2).endsWith("()")) { - for (ScopeNameCompletion completion : ScopeNameCompletionProposal.COMPLETIONS) { - ICompletionProposal proposal = new ScopeNameCompletionProposal(completion, doc, offset, offset, ""); - completions.add(proposal); - } - } - else if (node instanceof StringLiteral && node.getParent() instanceof Annotation) { - // case: @Scope("...") - if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) { - String prefix = doc.get(node.getStartPosition(), offset - node.getStartPosition()); - for (ScopeNameCompletion completion : ScopeNameCompletionProposal.COMPLETIONS) { - if (completion.getValue().startsWith(prefix)) { - ICompletionProposal proposal = new ScopeNameCompletionProposal(completion, doc, node.getStartPosition(), node.getStartPosition() + node.getLength(), prefix); - completions.add(proposal); - } - } - } - } - else if (node instanceof StringLiteral && node.getParent() instanceof MemberValuePair) { - MemberValuePair memberPair = (MemberValuePair) node.getParent(); - - // case: @Scope(value=<*>) - if ("value".equals(memberPair.getName().toString()) && node.toString().startsWith("\"") && node.toString().endsWith("\"")) { - String prefix = doc.get(node.getStartPosition(), offset - node.getStartPosition()); - for (ScopeNameCompletion completion : ScopeNameCompletionProposal.COMPLETIONS) { - if (completion.getValue().startsWith(prefix)) { - ICompletionProposal proposal = new ScopeNameCompletionProposal(completion, doc, node.getStartPosition(), node.getStartPosition() + node.getLength(), prefix); - completions.add(proposal); - } - } - } - } - } - catch (Exception e) { - e.printStackTrace(); - } + public List getCompletionCandidates(IJavaProject project) { + return SCOPE_COMPLETIONS; } +// @Override +// public void provideCompletions(ASTNode node, Annotation annotation, ITypeBinding type, +// int offset, TextDocument doc, Collection completions) { +// +// try { +// if (node instanceof SimpleName && node.getParent() instanceof MemberValuePair) { +// MemberValuePair memberPair = (MemberValuePair) node.getParent(); +// +// // case: @Scope(value=<*>) +// if ("value".equals(memberPair.getName().toString()) && memberPair.getValue().toString().equals("$missing$")) { +// for (ScopeNameCompletion completion : ScopeNameCompletionProposal.COMPLETIONS) { +// ICompletionProposal proposal = new ScopeNameCompletionProposal(completion, doc, offset, offset, ""); +// completions.add(proposal); +// } +// } +// } +// // case: @Scope(<*>) +// else if (node == annotation && doc.get(offset - 1, 2).endsWith("()")) { +// for (ScopeNameCompletion completion : ScopeNameCompletionProposal.COMPLETIONS) { +// ICompletionProposal proposal = new ScopeNameCompletionProposal(completion, doc, offset, offset, ""); +// completions.add(proposal); +// } +// } +// else if (node instanceof StringLiteral && node.getParent() instanceof Annotation) { +// // case: @Scope("...") +// if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) { +// String prefix = doc.get(node.getStartPosition(), offset - node.getStartPosition()); +// for (ScopeNameCompletion completion : ScopeNameCompletionProposal.COMPLETIONS) { +// if (completion.getValue().startsWith(prefix)) { +// ICompletionProposal proposal = new ScopeNameCompletionProposal(completion, doc, node.getStartPosition(), node.getStartPosition() + node.getLength(), prefix); +// completions.add(proposal); +// } +// } +// } +// } +// else if (node instanceof StringLiteral && node.getParent() instanceof MemberValuePair) { +// MemberValuePair memberPair = (MemberValuePair) node.getParent(); +// +// // case: @Scope(value=<*>) +// if ("value".equals(memberPair.getName().toString()) && node.toString().startsWith("\"") && node.toString().endsWith("\"")) { +// String prefix = doc.get(node.getStartPosition(), offset - node.getStartPosition()); +// for (ScopeNameCompletion completion : ScopeNameCompletionProposal.COMPLETIONS) { +// if (completion.getValue().startsWith(prefix)) { +// ICompletionProposal proposal = new ScopeNameCompletionProposal(completion, doc, node.getStartPosition(), node.getStartPosition() + node.getLength(), prefix); +// completions.add(proposal); +// } +// } +// } +// } +// } +// catch (Exception e) { +// e.printStackTrace(); +// } +// } + } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/editor/harness/PropertyIndexHarness.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/editor/harness/PropertyIndexHarness.java index 1721d8823..71ae613ca 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/editor/harness/PropertyIndexHarness.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/editor/harness/PropertyIndexHarness.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017, 2023 Pivotal, Inc. + * Copyright (c) 2017, 2024 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -46,9 +46,9 @@ public class PropertyIndexHarness { @Override public SpringPropertyIndex getIndex(IDocument doc) { synchronized (PropertyIndexHarness.this) { - if (index==null) { + if (index == null) { IClasspath classpath = testProject == null ? null : testProject.getClasspath(); - index = SpringPropertyIndex.builder(valueProviders).withClasspath(classpath).build(); + index = SpringPropertyIndex.builder(valueProviders).withClasspath(classpath).build(); for (ConfigurationMetadataProperty propertyInfo : datas.values()) { index.add(propertyInfo); } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/DependsOnCompletionProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/DependsOnCompletionProviderTest.java index e3f4886d0..0c0f4bd86 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/DependsOnCompletionProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/DependsOnCompletionProviderTest.java @@ -92,6 +92,12 @@ public class DependsOnCompletionProviderTest { assertCompletions("@DependsOn(<*>)", 2, "@DependsOn(\"bean1\"<*>)"); } + // TODO: not yet working, needs more groundwork due to the parser skipping these non-valid parts of the AST +// @Test +// public void testDependsOnCompletionWithoutQuotesWithoutPrefixWithoutClosingBracket() throws Exception { +// assertCompletions("@DependsOn(<*>", 2, "@DependsOn(\"bean1\")<*>"); +// } + @Test public void testDependsOnCompletionWithoutQuotesWithPrefix() throws Exception { assertCompletions("@DependsOn(be<*>)", 2, "@DependsOn(\"bean1\"<*>)"); @@ -102,11 +108,23 @@ public class DependsOnCompletionProviderTest { assertCompletions("@DependsOn(value=<*>)", 2, "@DependsOn(value=\"bean1\"<*>)"); } + // TODO: not yet working, needs more groundwork due to the parser skipping these non-valid parts of the AST +// @Test +// public void testDependsOnCompletionWithoutQuotesWithAttributeNameAndDefaultSpaces() throws Exception { +// assertCompletions("@DependsOn(value = <*>)", 2, "@DependsOn(value = \"bean1\"<*>)"); +// } + @Test public void testDependsOnCompletionInsideOfQuotesWithoutPrefix() throws Exception { assertCompletions("@DependsOn(\"<*>\")", 2, "@DependsOn(\"bean1<*>\")"); } + // TODO: not yet working, needs more groundwork due to the parser skipping these non-valid parts of the AST +// @Test +// public void testDependsOnCompletionOpeningQuoteOnlyWithoutPrefix() throws Exception { +// assertCompletions("@DependsOn(\"<*>)", 2, "@DependsOn(\"bean1<*>\")"); +// } + @Test public void testDependsOnCompletionWithoutQuotesWithoutPrefixInsideArray() throws Exception { assertCompletions("@DependsOn({<*>})", 2, "@DependsOn({\"bean1\"<*>})"); @@ -144,7 +162,7 @@ public class DependsOnCompletionProviderTest { @Test public void testDependsOnCompletionInsideOfQuotesWithPrefixAndReplacedPostfix() throws Exception { - assertCompletions("@DependsOn(\"be<*>xxx\")", 2, "@DependsOn(\"bean1<*>xxx\")"); + assertCompletions("@DependsOn(\"be<*>xxx\")", 2, "@DependsOn(\"bean1<*>\")"); } @Test diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/QualifierCompletionProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/QualifierCompletionProviderTest.java index 4e56907e5..047d2a6fb 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/QualifierCompletionProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/QualifierCompletionProviderTest.java @@ -140,7 +140,7 @@ public class QualifierCompletionProviderTest { @Test public void testQualifierCompletionInsideOfQuotesWithPrefixAndReplacedPostfix() throws Exception { - assertCompletions("@Qualifier(\"be<*>xxx\")", 2, "@Qualifier(\"bean1<*>xxx\")"); + assertCompletions("@Qualifier(\"be<*>xxx\")", 2, "@Qualifier(\"bean1<*>\")"); } private void assertCompletions(String completionLine, int noOfExpectedCompletions, String expectedCompletedLine) throws Exception { diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/scope/test/ScopeCompletionTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/scope/test/ScopeCompletionTest.java index 78037bd0c..2041ea96d 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/scope/test/ScopeCompletionTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/scope/test/ScopeCompletionTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017 Pivotal, Inc. + * Copyright (c) 2017, 2024 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -10,12 +10,13 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.scope.test; +import static org.junit.jupiter.api.Assertions.assertEquals; + import java.io.InputStream; +import java.nio.charset.Charset; import java.util.List; import org.apache.commons.io.IOUtils; - -import static org.junit.jupiter.api.Assertions.assertEquals; import org.eclipse.lsp4j.CompletionItem; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -50,10 +51,6 @@ public class ScopeCompletionTest { harness.intialize(null); } -// private IJavaProject getTestProject() { -// return testProject; -// } - @Test void testEmptyBracketsCompletion() throws Exception { prepareCase("@Scope(\"onClass\")", "@Scope(<*>)"); @@ -71,13 +68,13 @@ public class ScopeCompletionTest { void testEmptyStringLiteralCompletion() throws Exception { prepareCase("@Scope(\"onClass\")", "@Scope(\"<*>\")"); assertAnnotationCompletions( - "@Scope(\"application\"<*>)", - "@Scope(\"globalSession\"<*>)", - "@Scope(\"prototype\"<*>)", - "@Scope(\"request\"<*>)", - "@Scope(\"session\"<*>)", - "@Scope(\"singleton\"<*>)", - "@Scope(\"websocket\"<*>)"); + "@Scope(\"application<*>\")", + "@Scope(\"globalSession<*>\")", + "@Scope(\"prototype<*>\")", + "@Scope(\"request<*>\")", + "@Scope(\"session<*>\")", + "@Scope(\"singleton<*>\")", + "@Scope(\"websocket<*>\")"); } @Test @@ -97,20 +94,20 @@ public class ScopeCompletionTest { void testEmptyValueStringLiteralCompletion() throws Exception { prepareCase("@Scope(\"onClass\")", "@Scope(value=\"<*>\")"); assertAnnotationCompletions( - "@Scope(value=\"application\"<*>)", - "@Scope(value=\"globalSession\"<*>)", - "@Scope(value=\"prototype\"<*>)", - "@Scope(value=\"request\"<*>)", - "@Scope(value=\"session\"<*>)", - "@Scope(value=\"singleton\"<*>)", - "@Scope(value=\"websocket\"<*>)"); + "@Scope(value=\"application<*>\")", + "@Scope(value=\"globalSession<*>\")", + "@Scope(value=\"prototype<*>\")", + "@Scope(value=\"request<*>\")", + "@Scope(value=\"session<*>\")", + "@Scope(value=\"singleton<*>\")", + "@Scope(value=\"websocket<*>\")"); } @Test void testPrefixWithClosingQuotesCompletion() throws Exception { prepareCase("@Scope(\"onClass\")", "@Scope(\"pro<*>\")"); assertAnnotationCompletions( - "@Scope(\"prototype\"<*>)"); + "@Scope(\"prototype<*>\")"); } @Test @@ -123,7 +120,7 @@ public class ScopeCompletionTest { void testValuePrefixWithClosingQuotesCompletion() throws Exception { prepareCase("@Scope(\"onClass\")", "@Scope(value=\"pro<*>\")"); assertAnnotationCompletions( - "@Scope(value=\"prototype\"<*>)"); + "@Scope(value=\"prototype<*>\")"); } @Test @@ -136,7 +133,7 @@ public class ScopeCompletionTest { void testPrefixReplaceRestCompletion() throws Exception { prepareCase("@Scope(\"onClass\")", "@Scope(\"pro<*>something\")"); assertAnnotationCompletions( - "@Scope(\"prototype\"<*>)"); + "@Scope(\"prototype<*>\")"); } @Test @@ -147,7 +144,7 @@ public class ScopeCompletionTest { private void prepareCase(String selectedAnnotation, String annotationStatementBeforeTest) throws Exception { InputStream resource = this.getClass().getResourceAsStream("/test-projects/test-annotations/src/main/java/org/test/TestScopeCompletion.java"); - String content = IOUtils.toString(resource); + String content = IOUtils.toString(resource, Charset.defaultCharset()); content = content.replace(selectedAnnotation, annotationStatementBeforeTest); editor = new Editor(harness, content, LanguageId.JAVA); diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/value/test/ValueCompletionTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/value/test/ValueCompletionTest.java index 733348789..580e380da 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/value/test/ValueCompletionTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/value/test/ValueCompletionTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017, 2020 Pivotal, Inc. + * Copyright (c) 2017, 2024 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -14,6 +14,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; import java.io.InputStream; +import java.nio.charset.Charset; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -63,7 +64,6 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; public class ValueCompletionTest { @Autowired private BootLanguageServerHarness harness; - @Autowired private IJavaProject testProject; @Autowired private JavaProjectFinder projectFinder; private Editor editor; @@ -132,10 +132,6 @@ public class ValueCompletionTest { harness.intialize(null); } - private IJavaProject getTestProject() { - return testProject; - } - @Test void testPrefixIdentification() { ValueCompletionProcessor processor = new ValueCompletionProcessor(projectFinder, null, null); @@ -392,7 +388,7 @@ public class ValueCompletionTest { private void prepareCase(String selectedAnnotation, String annotationStatementBeforeTest) throws Exception { InputStream resource = this.getClass().getResourceAsStream("/test-projects/test-annotations/src/main/java/org/test/TestValueCompletion.java"); - String content = IOUtils.toString(resource); + String content = IOUtils.toString(resource, Charset.defaultCharset()); content = content.replace(selectedAnnotation, annotationStatementBeforeTest); editor = new Editor(harness, content, LanguageId.JAVA);