GH-1261: initial steps to include details about used annotations at injection points

This commit is contained in:
Martin Lippert
2024-06-05 14:51:20 +02:00
parent 9564981d5b
commit 6f9d1428ef
25 changed files with 1328 additions and 78 deletions

View File

@@ -0,0 +1,42 @@
/*******************************************************************************
* 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.commons.protocol.spring;
import java.util.Map;
/**
* @author Martin Lippert
*/
public class AnnotationMetadata {
private final String annotationType;
private final boolean isMetaAnnotation;
private final Map<String, String[]> attributes;
public AnnotationMetadata(String annotationType, boolean isMetaAnnotation, Map<String, String[]> attributes) {
this.annotationType = annotationType;
this.isMetaAnnotation = isMetaAnnotation;
this.attributes = attributes;
}
public String getAnnotationType() {
return annotationType;
}
public boolean isMetaAnnotation() {
return isMetaAnnotation;
}
public Map<String, String[]> getAttributes() {
return attributes;
}
}

View File

@@ -23,14 +23,13 @@ public class Bean {
private final Location location;
private final InjectionPoint[] injectionPoints;
private final Set<String> supertypes;
private final String[] annotations;
private final AnnotationMetadata[] annotations;
public Bean(String name, String type, Location location, InjectionPoint[] injectionPoints, Set<String> supertypes, String[] annotations) {
public Bean(String name, String type, Location location, InjectionPoint[] injectionPoints, Set<String> supertypes, AnnotationMetadata[] annotations) {
this.name = name;
this.type = type;
this.location = location;
this.annotations = annotations;
if (injectionPoints != null && injectionPoints.length == 0) {
this.injectionPoints = DefaultValues.EMPTY_INJECTION_POINTS;
}
@@ -48,6 +47,12 @@ public class Bean {
this.supertypes = supertypes;
}
if (annotations != null && annotations.length == 0) {
this.annotations = DefaultValues.EMPTY_ANNOTATIONS;
}
else {
this.annotations = annotations;
}
}
public String getName() {
@@ -70,7 +75,7 @@ public class Bean {
return type != null && ((this.type != null && this.type.equals(type)) || (supertypes.contains(type)));
}
public String[] getAnnotations() {
public AnnotationMetadata[] getAnnotations() {
return annotations;
}

View File

@@ -17,7 +17,6 @@ public class DefaultValues {
public static final Set<String> EMPTY_SUPERTYPES = new HashSet<>();
public static final Set<String> OBJECT_SUPERTYPE = Set.of("java.lang.Object");
public static final AnnotationMetadata[] EMPTY_ANNOTATIONS = new AnnotationMetadata[0];
public static final InjectionPoint[] EMPTY_INJECTION_POINTS = new InjectionPoint[0];
}

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2023 VMware, Inc.
* Copyright (c) 2023, 2024 VMware, 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
@@ -12,17 +12,29 @@ package org.springframework.ide.vscode.commons.protocol.spring;
import org.eclipse.lsp4j.Location;
/**
* @author Martin Lippert
*/
public class InjectionPoint {
private final String name;
private final String type;
private final Location location;
private final AnnotationMetadata[] annotations;
public InjectionPoint(String name, String type, Location location) {
public InjectionPoint(String name, String type, Location location, AnnotationMetadata[] annotations) {
super();
this.name = name;
this.type = type;
this.location = location;
if (annotations == null || (annotations != null && annotations.length == 0)) {
this.annotations = DefaultValues.EMPTY_ANNOTATIONS;
}
else {
this.annotations = annotations;
}
}
public String getName() {
@@ -36,5 +48,9 @@ public class InjectionPoint {
public Location getLocation() {
return location;
}
public AnnotationMetadata[] getAnnotations() {
return annotations;
}
}

View File

@@ -25,6 +25,7 @@ import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
import org.springframework.ide.vscode.boot.java.Annotations;
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.data.DataRepositoryCompletionProcessor;
import org.springframework.ide.vscode.boot.java.handlers.BootJavaCompletionEngine;
import org.springframework.ide.vscode.boot.java.handlers.CompletionProvider;
@@ -113,6 +114,7 @@ public class BootJavaCompletionEngineConfigurer {
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());
return new BootJavaCompletionEngine(cuCache, providers, snippetManager);

View File

@@ -46,6 +46,7 @@ import org.springframework.ide.vscode.boot.index.cache.IndexCacheOnDisc;
import org.springframework.ide.vscode.boot.index.cache.IndexCacheVoid;
import org.springframework.ide.vscode.boot.java.JavaDefinitionHandler;
import org.springframework.ide.vscode.boot.java.beans.DependsOnDefinitionProvider;
import org.springframework.ide.vscode.boot.java.beans.QualifierDefinitionProvider;
import org.springframework.ide.vscode.boot.java.handlers.BootJavaCodeActionProvider;
import org.springframework.ide.vscode.boot.java.handlers.BootJavaReconcileEngine;
import org.springframework.ide.vscode.boot.java.handlers.JavaCodeActionHandler;
@@ -393,7 +394,10 @@ public class BootLanguageServerBootApp {
@Bean
JavaDefinitionHandler javaDefinitionHandler(CompilationUnitCache cuCache, JavaProjectFinder projectFinder, SpringMetamodelIndex springIndex) {
return new JavaDefinitionHandler(cuCache, projectFinder, List.of(new PropertyValueAnnotationDefProvider(), new DependsOnDefinitionProvider(springIndex)));
return new JavaDefinitionHandler(cuCache, projectFinder, List.of(
new PropertyValueAnnotationDefProvider(),
new DependsOnDefinitionProvider(springIndex),
new QualifierDefinitionProvider(springIndex)));
}
@Bean

View File

@@ -35,7 +35,9 @@ import org.eclipse.lsp4j.Location;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.handlers.SymbolAddOnInformation;
import org.springframework.ide.vscode.commons.protocol.spring.AnnotationMetadata;
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
import org.springframework.ide.vscode.commons.protocol.spring.DefaultValues;
import org.springframework.ide.vscode.commons.protocol.spring.InjectionPoint;
import org.springframework.ide.vscode.commons.util.UriUtil;
@@ -351,6 +353,7 @@ public class IndexCacheOnDisc implements IndexCache {
return new GsonBuilder()
.registerTypeAdapter(SymbolAddOnInformation.class, new SymbolAddOnInformationAdapter())
.registerTypeAdapter(Bean.class, new BeanJsonAdapter())
.registerTypeAdapter(InjectionPoint.class, new InjectionPointJsonAdapter())
.registerTypeAdapter(IndexCacheStore.class, new IndexCacheStoreAdapter())
.create();
}
@@ -472,10 +475,29 @@ public class IndexCacheOnDisc implements IndexCache {
Set<String> supertypes = context.deserialize(supertypesObject, Set.class);
JsonElement annotationsObject = parsedObject.get("annotations");
String[] annotations = annotationsObject == null? new String[0] : context.deserialize(annotationsObject, String[].class);
AnnotationMetadata[] annotations = annotationsObject == null ? DefaultValues.EMPTY_ANNOTATIONS : context.deserialize(annotationsObject, AnnotationMetadata[].class);
return new Bean(beanName, beanType, location, injectionPoints, supertypes, annotations);
}
}
private static class InjectionPointJsonAdapter implements JsonDeserializer<InjectionPoint> {
@Override
public InjectionPoint deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
JsonObject parsedObject = json.getAsJsonObject();
String injectionPointName = parsedObject.get("name").getAsString();
String injectionPointType = parsedObject.get("type").getAsString();
JsonElement locationObject = parsedObject.get("location");
Location location = context.deserialize(locationObject, Location.class);
JsonElement annotationsObject = parsedObject.get("annotations");
AnnotationMetadata[] annotations = annotationsObject == null ? DefaultValues.EMPTY_ANNOTATIONS : context.deserialize(annotationsObject, AnnotationMetadata[].class);
return new InjectionPoint(injectionPointName, injectionPointType, location, annotations);
}
}
}

View File

@@ -32,7 +32,6 @@ import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.Annotations;
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies;
import org.springframework.ide.vscode.boot.java.handlers.AbstractSymbolProvider;
import org.springframework.ide.vscode.boot.java.handlers.EnhancedSymbolInformation;
import org.springframework.ide.vscode.boot.java.handlers.SymbolAddOnInformation;
@@ -40,6 +39,7 @@ import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
import org.springframework.ide.vscode.boot.java.utils.CachedSymbol;
import org.springframework.ide.vscode.boot.java.utils.FunctionUtils;
import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext;
import org.springframework.ide.vscode.commons.protocol.spring.AnnotationMetadata;
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
import org.springframework.ide.vscode.commons.protocol.spring.InjectionPoint;
import org.springframework.ide.vscode.commons.util.BadLocationException;
@@ -95,9 +95,16 @@ public class BeansSymbolProvider extends AbstractSymbolProvider {
Set<String> supertypes = new HashSet<>();
ASTUtils.findSupertypes(beanType, supertypes);
String[] annotations = AnnotationHierarchies
.findTransitiveSuperAnnotationBindings(node.resolveAnnotationBinding())
.map(t -> t.getAnnotationType().getQualifiedName()).toArray(String[]::new);
Collection<Annotation> annotationsOnMethod = ASTUtils.getAnnotations(method);
AnnotationMetadata[] annotations = annotationsOnMethod.stream()
.map(an -> an.resolveAnnotationBinding())
.map(t -> new AnnotationMetadata(t.getAnnotationType().getQualifiedName(), false, ASTUtils.getAttributes(t)))
.toArray(AnnotationMetadata[]::new);
// AnnotationMetadata[] annotations = AnnotationHierarchies
// .findTransitiveSuperAnnotationBindings(node.resolveAnnotationBinding())
// .map(t -> new AnnotationMetadata(t.getAnnotationType().getQualifiedName(), false, getAttributes(t)))
// .toArray(AnnotationMetadata[]::new);
Bean beanDefinition = new Bean(nameAndRegion.getT1(), beanType.getQualifiedName(), location, injectionPoints, supertypes, annotations);

View File

@@ -34,6 +34,7 @@ import org.springframework.ide.vscode.boot.java.handlers.SymbolAddOnInformation;
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
import org.springframework.ide.vscode.boot.java.utils.CachedSymbol;
import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext;
import org.springframework.ide.vscode.commons.protocol.spring.AnnotationMetadata;
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
import org.springframework.ide.vscode.commons.protocol.spring.InjectionPoint;
import org.springframework.ide.vscode.commons.util.BadLocationException;
@@ -66,6 +67,7 @@ public class ComponentSymbolProvider extends AbstractSymbolProvider {
protected Tuple.Two<EnhancedSymbolInformation, Bean> createSymbol(Annotation node, ITypeBinding annotationType, Collection<ITypeBinding> metaAnnotations, TextDocument doc) throws BadLocationException {
String annotationTypeName = annotationType.getName();
Collection<String> metaAnnotationNames = metaAnnotations.stream()
.map(ITypeBinding::getName)
.collect(Collectors.toList());
@@ -94,8 +96,17 @@ public class ComponentSymbolProvider extends AbstractSymbolProvider {
Set<String> supertypes = new HashSet<>();
ASTUtils.findSupertypes(beanType, supertypes);
String[] annotations = Stream.concat(Stream.of(annotationType), metaAnnotations.stream()).map(t -> t.getQualifiedName()).toArray(String[]::new);
Collection<Annotation> annotationsOnType = ASTUtils.getAnnotations(type);
AnnotationMetadata[] annotations = Stream.concat(
annotationsOnType.stream()
.map(an -> an.resolveAnnotationBinding())
.map(t -> new AnnotationMetadata(t.getAnnotationType().getQualifiedName(), false, ASTUtils.getAttributes(t)))
,
metaAnnotations.stream()
.map(an -> new AnnotationMetadata(an.getQualifiedName(), true, null)))
.toArray(AnnotationMetadata[]::new);
Bean beanDefinition = new Bean(beanName, beanType.getQualifiedName(), location, injectionPoints, supertypes, annotations);
return Tuple.two(new EnhancedSymbolInformation(symbol, addon), beanDefinition);

View File

@@ -40,6 +40,7 @@ import org.springframework.ide.vscode.boot.java.handlers.SymbolAddOnInformation;
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
import org.springframework.ide.vscode.boot.java.utils.CachedSymbol;
import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext;
import org.springframework.ide.vscode.commons.protocol.spring.AnnotationMetadata;
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
import org.springframework.ide.vscode.commons.protocol.spring.InjectionPoint;
import org.springframework.ide.vscode.commons.util.BadLocationException;
@@ -67,8 +68,7 @@ public class FeignClientSymbolProvider extends AbstractSymbolProvider {
}
}
private Two<EnhancedSymbolInformation, Bean> createSymbol(Annotation node, ITypeBinding annotationType,
Collection<ITypeBinding> metaAnnotations, TextDocument doc) throws BadLocationException {
private Two<EnhancedSymbolInformation, Bean> createSymbol(Annotation node, ITypeBinding annotationType, Collection<ITypeBinding> metaAnnotations, TextDocument doc) throws BadLocationException {
String annotationTypeName = annotationType.getName();
Collection<String> metaAnnotationNames = metaAnnotations.stream()
.map(ITypeBinding::getName)
@@ -92,8 +92,17 @@ public class FeignClientSymbolProvider extends AbstractSymbolProvider {
Set<String> supertypes = new HashSet<>();
ASTUtils.findSupertypes(beanType, supertypes);
String[] annotations = Stream.concat(Stream.of(annotationType), metaAnnotations.stream()).map(t -> t.getQualifiedName()).toArray(String[]::new);
Collection<Annotation> annotationsOnType = ASTUtils.getAnnotations(type);
AnnotationMetadata[] annotations = Stream.concat(
annotationsOnType.stream()
.map(an -> an.resolveAnnotationBinding())
.map(t -> new AnnotationMetadata(t.getAnnotationType().getQualifiedName(), false, ASTUtils.getAttributes(t)))
,
metaAnnotations.stream()
.map(an -> new AnnotationMetadata(an.getQualifiedName(), true, null)))
.toArray(AnnotationMetadata[]::new);
Bean beanDefinition = new Bean(beanName, beanType == null ? "" : beanType.getQualifiedName(), location, injectionPoints, supertypes, annotations);
return Tuple.two(new EnhancedSymbolInformation(symbol, addon), beanDefinition);

View File

@@ -0,0 +1,275 @@
/*******************************************************************************
* 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.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.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 QualifierCompletionProcessor implements CompletionProvider {
private final JavaProjectFinder projectFinder;
private final SpringMetamodelIndex springIndex;
public QualifierCompletionProcessor(JavaProjectFinder projectFinder, SpringMetamodelIndex springIndex) {
this.projectFinder = projectFinder;
this.springIndex = springIndex;
}
@Override
public void provideCompletions(ASTNode node, Annotation annotation, ITypeBinding type, int offset, TextDocument doc, Collection<ICompletionProposal> completions) {
Optional<IJavaProject> optionalProject = projectFinder.find(doc.getId());
if (!optionalProject.isPresent()) {
return;
}
IJavaProject project = optionalProject.get();
try {
// case: @Qualifier(<*>)
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() + "\"");
// PT-160455522: create a proposal with `PlainText` format type, because for vscode (but not Eclipse), if you send it as a snippet
// and it is "place holder" as such `"${debug}"`, vscode may treat it as a snippet place holder, and insert an empty string
// if it cannot resolve it. If sending this as plain text, then insertion happens correctly
QualifierCompletionProposal proposal = new QualifierCompletionProposal(edits, bean.getName(), bean.getName(), null);
completions.add(proposal);
}
}
// case: @Qualifier(prefix<*>)
else if (node instanceof SimpleName && node.getParent() instanceof Annotation) {
computeProposalsForSimpleName(project, node, completions, offset, doc);
}
// case: @Qualifier(value=<*>)
else if (node instanceof SimpleName && node.getParent() instanceof MemberValuePair
&& "value".equals(((MemberValuePair)node.getParent()).getName().toString())) {
computeProposalsForSimpleName(project, node, completions, offset, doc);
}
// case: @Qualifier("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: @Qualifier(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: @Qualifier({<*>})
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<ICompletionProposal> 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<String> mentionedBeans = alreadyMentionedBeans(node);
Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName());
List<Bean> 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);
// PT-160455522: create a proposal with `PlainText` format type, because for vscode (but not Eclipse), if you send it as a snippet
// and it is "place holder" as such `"${debug}"`, vscode may treat it as a snippet place holder, and insert an empty string
// if it cannot resolve it. If sending this as plain text, then insertion happens correctly
QualifierCompletionProposal proposal = new QualifierCompletionProposal(edits, bean.getName(), bean.getName(), null);
completions.add(proposal);
}
}
private void computeProposalsForStringLiteral(IJavaProject project, ASTNode node, Collection<ICompletionProposal> 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<String> mentionedBeans = alreadyMentionedBeans(node);
Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName());
final String filterPrefix = prefix;
List<Bean> 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());
// PT-160455522: create a proposal with `PlainText` format type, because for vscode (but not Eclipse), if you send it as a snippet
// and it is "place holder" as such `"${debug}"`, vscode may treat it as a snippet place holder, and insert an empty string
// if it cannot resolve it. If sending this as plain text, then insertion happens correctly
QualifierCompletionProposal proposal = new QualifierCompletionProposal(edits, bean.getName(), bean.getName(), null);
completions.add(proposal);
}
}
private void computeProposalsForArrayInitializr(IJavaProject project, ArrayInitializer node, Collection<ICompletionProposal> completions, int offset, IDocument doc) {
Set<String> mentionedBeans = alreadyMentionedBeans(node);
Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName());
List<Bean> 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() + "\"");
// PT-160455522: create a proposal with `PlainText` format type, because for vscode (but not Eclipse), if you send it as a snippet
// and it is "place holder" as such `"${debug}"`, vscode may treat it as a snippet place holder, and insert an empty string
// if it cannot resolve it. If sending this as plain text, then insertion happens correctly
QualifierCompletionProposal proposal = new QualifierCompletionProposal(edits, bean.getName(), bean.getName(), null);
completions.add(proposal);
}
}
private void computeProposalsForInsideArrayInitializer(IJavaProject project, ASTNode node, Collection<ICompletionProposal> completions, int offset, TextDocument doc) throws BadLocationException {
int length = offset - (node.getStartPosition() + 1);
if (length >= 0) {
computeProposalsForStringLiteral(project, node, completions, offset, doc);
}
else {
Set<String> mentionedBeans = alreadyMentionedBeans(node);
Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName());
List<Bean> 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() + "\",");
// PT-160455522: create a proposal with `PlainText` format type, because for vscode (but not Eclipse), if you send it as a snippet
// and it is "place holder" as such `"${debug}"`, vscode may treat it as a snippet place holder, and insert an empty string
// if it cannot resolve it. If sending this as plain text, then insertion happens correctly
QualifierCompletionProposal proposal = new QualifierCompletionProposal(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<String> alreadyMentionedBeans(ASTNode node) {
Set<String> 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;
}
}

View File

@@ -0,0 +1,65 @@
/*******************************************************************************
* 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 org.eclipse.lsp4j.CompletionItemKind;
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
import org.springframework.ide.vscode.commons.util.Renderable;
/**
* @author Martin Lippert
*/
public class QualifierCompletionProposal implements ICompletionProposal {
private static final String EMPTY_DETAIL = "";
private DocumentEdits edits;
private String label;
private String detail;
private Renderable documentation;
public QualifierCompletionProposal(DocumentEdits edits, String label, String detail, Renderable documentation) {
this.edits = edits;
this.label = label;
// PT 161489998 - Detail for proposal must not be null. For some clients like Eclipse,
// a null detail results in an NPE at JDT level when inserting the proposal in the editor, and results
// in odd behaviour like insertion of an extra new line.
this.detail = detail == null ? EMPTY_DETAIL : detail;
this.documentation = documentation;
}
@Override
public String getLabel() {
return this.label;
}
@Override
public CompletionItemKind getKind() {
return CompletionItemKind.Value;
}
@Override
public DocumentEdits getTextEdit() {
return this.edits;
}
@Override
public String getDetail() {
return this.detail;
}
@Override
public Renderable getDocumentation() {
return this.documentation;
}
}

View File

@@ -0,0 +1,75 @@
/*******************************************************************************
* 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.Collections;
import java.util.List;
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.CompilationUnit;
import org.eclipse.jdt.core.dom.IAnnotationBinding;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.lsp4j.LocationLink;
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
import org.springframework.ide.vscode.boot.java.Annotations;
import org.springframework.ide.vscode.boot.java.IJavaDefinitionProvider;
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
/**
* @author Martin Lippert
*/
public class QualifierDefinitionProvider implements IJavaDefinitionProvider {
private final SpringMetamodelIndex springIndex;
public QualifierDefinitionProvider(SpringMetamodelIndex springIndex) {
this.springIndex = springIndex;
}
@Override
public List<LocationLink> getDefinitions(CancelChecker cancelToken, IJavaProject project, CompilationUnit cu, ASTNode n) {
if (n instanceof StringLiteral) {
StringLiteral valueNode = (StringLiteral) n;
ASTNode parent = ASTUtils.getNearestAnnotationParent(valueNode);
if (parent != null && parent instanceof Annotation) {
Annotation a = (Annotation) parent;
IAnnotationBinding binding = a.resolveAnnotationBinding();
if (binding != null && binding.getAnnotationType() != null && Annotations.QUALIFIER.equals(binding.getAnnotationType().getQualifiedName())) {
String beanName = valueNode.getLiteralValue();
if (beanName != null && beanName.length() > 0) {
return findBeansWithName(project, beanName);
}
}
}
}
return Collections.emptyList();
}
private List<LocationLink> findBeansWithName(IJavaProject project, String beanName) {
Bean[] beans = this.springIndex.getBeansWithName(project.getElementName(), beanName);
return Arrays.stream(beans)
.map(bean -> {
return new LocationLink(bean.getLocation().getUri(), bean.getLocation().getRange(), bean.getLocation().getRange());
})
.collect(Collectors.toList());
}
}

View File

@@ -10,9 +10,11 @@
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.data;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.lsp4j.Location;
@@ -30,6 +32,7 @@ import org.springframework.ide.vscode.boot.java.handlers.SymbolAddOnInformation;
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
import org.springframework.ide.vscode.boot.java.utils.CachedSymbol;
import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext;
import org.springframework.ide.vscode.commons.protocol.spring.AnnotationMetadata;
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
import org.springframework.ide.vscode.commons.protocol.spring.InjectionPoint;
import org.springframework.ide.vscode.commons.util.BadLocationException;
@@ -73,7 +76,14 @@ public class DataRepositorySymbolProvider extends AbstractSymbolProvider {
ASTUtils.findSupertypes(concreteBeanTypeBindung, supertypes);
String concreteRepoType = concreteBeanTypeBindung.getQualifiedName();
Bean beanDefinition = new Bean(beanName, concreteRepoType, location, injectionPoints, supertypes, new String[0]);
Collection<Annotation> annotationsOnMethod = ASTUtils.getAnnotations(typeDeclaration);
AnnotationMetadata[] annotations = annotationsOnMethod.stream()
.map(an -> an.resolveAnnotationBinding())
.map(t -> new AnnotationMetadata(t.getAnnotationType().getQualifiedName(), false, ASTUtils.getAttributes(t)))
.toArray(AnnotationMetadata[]::new);
Bean beanDefinition = new Bean(beanName, concreteRepoType, location, injectionPoints, supertypes, annotations);
context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), enhancedSymbol));
context.getBeans().add(new CachedBean(context.getDocURI(), beanDefinition));

View File

@@ -24,7 +24,6 @@ import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.IAnnotationBinding;
import org.eclipse.jdt.core.dom.IMemberValuePairBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.MemberValuePair;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.NormalAnnotation;
@@ -186,40 +185,17 @@ public class RequestMappingSymbolProvider extends AbstractSymbolProvider {
IAnnotationBinding annotationBinding = getAnnotationFromSupertypes(node, context);
IMemberValuePairBinding valuePair = getValuePair(annotationBinding, attributeNames);
if (valuePair != null) {
Object value = valuePair.getValue();
if (value instanceof Object[]) {
Object[] values = (Object[]) value;
String[] result = new String[values.length];
for (int k = 0; k < result.length; k++) {
Object v = values[k];
if (v instanceof IVariableBinding) {
IVariableBinding varBinding = (IVariableBinding) v;
result[k] = varBinding.getName();
ITypeBinding klass = varBinding.getDeclaringClass();
if (klass!=null) {
context.addDependency(klass);
}
}
else if (v instanceof String) {
result[k] = (String) v;
}
}
return result;
ASTUtils.MemberValuePairAndType result = ASTUtils.getValuesFromValuePair(valuePair);
if (result != null) {
if (result.dereferencedType != null) {
context.addDependency(result.dereferencedType);
}
else if (value instanceof String[]) {
return (String[]) value;
}
else if (value != null) {
return new String[] {value.toString()};
}
return result.values;
}
else {
return null;
}
return null;
}
private IMemberValuePairBinding getValuePair(IAnnotationBinding annotationBinding, Set<String> names) {

View File

@@ -13,7 +13,9 @@ package org.springframework.ide.vscode.boot.java.utils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
@@ -26,7 +28,9 @@ import org.eclipse.jdt.core.dom.ArrayInitializer;
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.IAnnotationBinding;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IMemberValuePairBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.MemberValuePair;
@@ -47,6 +51,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.Annotations;
import org.springframework.ide.vscode.boot.java.jdt.imports.ImportRewrite;
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
import org.springframework.ide.vscode.commons.protocol.spring.AnnotationMetadata;
import org.springframework.ide.vscode.commons.protocol.spring.DefaultValues;
import org.springframework.ide.vscode.commons.protocol.spring.InjectionPoint;
import org.springframework.ide.vscode.commons.util.BadLocationException;
@@ -256,8 +261,15 @@ public class ASTUtils {
}
public static Collection<Annotation> getAnnotations(TypeDeclaration declaringType) {
Object modifiersObj = declaringType.getStructuralProperty(TypeDeclaration.MODIFIERS2_PROPERTY);
public static Collection<Annotation> getAnnotations(TypeDeclaration typeDeclaration) {
return getAnnotationsFromModifiers(typeDeclaration.getStructuralProperty(TypeDeclaration.MODIFIERS2_PROPERTY));
}
public static Collection<Annotation> getAnnotations(MethodDeclaration methodDeclaration) {
return getAnnotationsFromModifiers(methodDeclaration.getStructuralProperty(MethodDeclaration.MODIFIERS2_PROPERTY));
}
private static Collection<Annotation> getAnnotationsFromModifiers(Object modifiersObj) {
if (modifiersObj instanceof List) {
ImmutableList.Builder<Annotation> annotations = ImmutableList.builder();
for (Object node : (List<?>)modifiersObj) {
@@ -335,13 +347,17 @@ public class ASTUtils {
if (object instanceof VariableDeclaration) {
VariableDeclaration variable = (VariableDeclaration) object;
String name = variable.getName().toString();
String type = variable.resolveBinding().getType().getQualifiedName();
IVariableBinding variableBinding = variable.resolveBinding();
String type = variableBinding.getType().getQualifiedName();
DocumentRegion region = ASTUtils.nodeRegion(doc, variable.getName());
Range range = doc.toRange(region);
Location location = new Location(doc.getUri(), range);
result.add(new InjectionPoint(name, type, location));
AnnotationMetadata[] annotations = ASTUtils.getAnnotationsMetadata(variableBinding.getAnnotations());
result.add(new InjectionPoint(name, type, location, annotations));
}
}
return result;
@@ -392,13 +408,17 @@ public class ASTUtils {
if (object instanceof VariableDeclaration) {
VariableDeclaration variable = (VariableDeclaration) object;
String name = variable.getName().toString();
String type = variable.resolveBinding().getType().getQualifiedName();
IVariableBinding variableBinding = variable.resolveBinding();
String type = variableBinding.getType().getQualifiedName();
DocumentRegion region = ASTUtils.nodeRegion(doc, variable.getName());
Range range = doc.toRange(region);
Location location = new Location(doc.getUri(), range);
result.add(new InjectionPoint(name, type, location));
AnnotationMetadata[] annotations = ASTUtils.getAnnotationsMetadata(variableBinding.getAnnotations());
result.add(new InjectionPoint(name, type, location, annotations));
}
}
@@ -419,11 +439,14 @@ public class ASTUtils {
for (FieldDeclaration field : fields) {
boolean autowiredField = false;
List<Annotation> fieldAnnotations = new ArrayList<>();
List<?> modifiers = field.modifiers();
for (Object modifier : modifiers) {
if (modifier instanceof Annotation) {
Annotation annotation = (Annotation) modifier;
fieldAnnotations.add(annotation);
String qualifiedName = annotation.resolveTypeBinding().getQualifiedName();
if (Annotations.AUTOWIRED.equals(qualifiedName)) {
@@ -445,7 +468,12 @@ public class ASTUtils {
String fieldType = field.getType().resolveBinding().getQualifiedName();
result.add(new InjectionPoint(fieldName, fieldType, fieldLocation));
AnnotationMetadata[] annotationsMetadata = fieldAnnotations.stream()
.map(an -> an.resolveAnnotationBinding())
.map(t -> new AnnotationMetadata(t.getAnnotationType().getQualifiedName(), false, ASTUtils.getAttributes(t)))
.toArray(AnnotationMetadata[]::new);
result.add(new InjectionPoint(fieldName, fieldType, fieldLocation, annotationsMetadata));
}
}
}
@@ -454,11 +482,82 @@ public class ASTUtils {
return result.size() > 0 ? result.toArray(new InjectionPoint[result.size()]) : DefaultValues.EMPTY_INJECTION_POINTS;
}
private static AnnotationMetadata[] getAnnotationsMetadata(IAnnotationBinding[] annotations) {
return Arrays.stream(annotations)
.map(t -> new AnnotationMetadata(t.getAnnotationType().getQualifiedName(), false, getAttributes(t)))
.toArray(AnnotationMetadata[]::new);
}
public static Map<String, String[]> getAttributes(IAnnotationBinding t) {
Map<String, String[]> result = new LinkedHashMap<>();
IMemberValuePairBinding[] pairs = t.getDeclaredMemberValuePairs();
for (IMemberValuePairBinding pair : pairs) {
MemberValuePairAndType values = ASTUtils.getValuesFromValuePair(pair);
if (values != null) {
result.put(pair.getName(), values.values);
}
}
return result;
}
public static ASTNode getNearestAnnotationParent(ASTNode node) {
while (node != null && !(node instanceof Annotation)) {
node = node.getParent();
}
return node;
}
public static MemberValuePairAndType getValuesFromValuePair(IMemberValuePairBinding valuePair) {
if (valuePair != null) {
Object value = valuePair.getValue();
MemberValuePairAndType result = new MemberValuePairAndType();
if (value instanceof Object[]) {
Object[] values = (Object[]) value;
result.values = new String[values.length];
for (int k = 0; k < values.length; k++) {
Object v = values[k];
if (v instanceof IVariableBinding) {
IVariableBinding varBinding = (IVariableBinding) v;
result.values[k] = varBinding.getName();
ITypeBinding klass = varBinding.getDeclaringClass();
if (klass != null) {
result.dereferencedType= klass;
}
}
else if (v instanceof String) {
result.values[k] = (String) v;
}
else if (v instanceof ITypeBinding) {
result.values[k] = ((ITypeBinding) v).getQualifiedName();
}
else if (v != null) {
result.values[k] = v.toString();
}
}
return result;
}
else if (value instanceof String[]) {
result.values = (String[]) value;
return result;
}
else if (value != null) {
result.values = new String[] {value.toString()};
return result;
}
}
return null;
}
public static class MemberValuePairAndType {
public String[] values;
public ITypeBinding dereferencedType;
}
}

View File

@@ -18,7 +18,9 @@ import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.assertj.core.util.Arrays;
@@ -28,6 +30,7 @@ import org.eclipse.lsp4j.Range;
import org.junit.jupiter.api.Test;
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
import org.springframework.ide.vscode.boot.index.cache.IndexCacheOnDisc;
import org.springframework.ide.vscode.commons.protocol.spring.AnnotationMetadata;
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
import org.springframework.ide.vscode.commons.protocol.spring.DefaultValues;
import org.springframework.ide.vscode.commons.protocol.spring.InjectionPoint;
@@ -38,7 +41,9 @@ public class SpringMetamodelIndexTest {
private InjectionPoint[] emptyInjectionPoints = new InjectionPoint[0];
private Set<String> emptySupertypes = new HashSet<>();
private String[] emptyAnnotations = new String[0];
private AnnotationMetadata[] emptyAnnotations = new AnnotationMetadata[0];
private AnnotationMetadata[] emptyInjectionAnnotations = new AnnotationMetadata[0];
private Map<String, String[]> emptyAnnotationAttributes = new LinkedHashMap<>();
private Location locationForDoc1 = new Location("docURI1", new Range(new Position(1, 1), new Position(1, 10)));
private Location locationForDoc2 = new Location("docURI2", new Range(new Position(2, 1), new Position(2, 10)));
@@ -220,8 +225,10 @@ public class SpringMetamodelIndexTest {
@Test
void testOverallSerializeDeserializeBeans() {
InjectionPoint point1 = new InjectionPoint("point1", "point1-type", locationForDoc2);
InjectionPoint point2 = new InjectionPoint("point2", "point2-type", locationForDoc1);
InjectionPoint point1 = new InjectionPoint("point1", "point1-type", locationForDoc2, new AnnotationMetadata[]
{new AnnotationMetadata("anno1", false, emptyAnnotationAttributes),new AnnotationMetadata("anno2", false, emptyAnnotationAttributes)});
InjectionPoint point2 = new InjectionPoint("point2", "point2-type", locationForDoc1, null);
Bean bean1 = new Bean("beanName1", "beanType", locationForDoc1, new InjectionPoint[] {point1, point2}, Set.of("supertype1", "supertype2"), emptyAnnotations);
String serialized = bean1.toString();
@@ -247,6 +254,12 @@ public class SpringMetamodelIndexTest {
assertTrue(deserializedBean.isTypeCompatibleWith("supertype1"));
assertTrue(deserializedBean.isTypeCompatibleWith("supertype2"));
assertFalse(deserializedBean.isTypeCompatibleWith("java.lang.String"));
assertEquals(2, points[0].getAnnotations().length);
assertEquals("anno1", points[0].getAnnotations()[0].getAnnotationType());
assertEquals("anno2", points[0].getAnnotations()[1].getAnnotationType());
assertEquals(0, points[1].getAnnotations().length);
}
@Test
@@ -270,6 +283,12 @@ public class SpringMetamodelIndexTest {
assertSame(DefaultValues.EMPTY_INJECTION_POINTS, bean1.getInjectionPoints());
}
@Test
void testEmptyAnnotationOptimization() {
InjectionPoint point = new InjectionPoint("pointName", "pointType", locationForDoc1, emptyInjectionAnnotations);
assertSame(DefaultValues.EMPTY_ANNOTATIONS, point.getAnnotations());
}
@Test
void testFindNoMatchingBeansWithEmptySupertypes() {
SpringMetamodelIndex index = new SpringMetamodelIndex();

View File

@@ -10,6 +10,7 @@
*******************************************************************************/
package org.springframework.ide.vscode.boot.index.test;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -17,6 +18,7 @@ import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.File;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@@ -34,6 +36,7 @@ import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest;
import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf;
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.protocol.spring.AnnotationMetadata;
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
import org.springframework.ide.vscode.commons.protocol.spring.DefaultValues;
import org.springframework.ide.vscode.commons.protocol.spring.InjectionPoint;
@@ -90,7 +93,7 @@ public class SpringMetamodelIndexerBeansTest {
Bean[] beans = springIndex.getBeansWithName("test-spring-indexing", "bean1");
String docUri = directory.toPath().resolve("src/main/java/org/test/MainClass.java").toUri().toString();
Location location = new Location(docUri, new Range(new Position(13, 1), new Position(13, 6)));
Location location = new Location(docUri, new Range(new Position(15, 1), new Position(15, 6)));
assertEquals(location, beans[0].getLocation());
}
@@ -194,12 +197,12 @@ public class SpringMetamodelIndexerBeansTest {
assertEquals("bean1", injectionPoints[0].getName());
assertEquals("org.test.BeanClass1", injectionPoints[0].getType());
Location ip1Location = new Location(docUri, new Range(new Position(10, 31), new Position(10, 36)));
Location ip1Location = new Location(docUri, new Range(new Position(12, 20), new Position(12, 25)));
assertEquals(ip1Location, injectionPoints[0].getLocation());
assertEquals("bean2", injectionPoints[1].getName());
assertEquals("org.test.BeanClass2", injectionPoints[1].getType());
Location ip2Location = new Location(docUri, new Range(new Position(11, 31), new Position(11, 36)));
Location ip2Location = new Location(docUri, new Range(new Position(16, 20), new Position(16, 25)));
assertEquals(ip2Location, injectionPoints[1].getLocation());
}
@@ -208,13 +211,15 @@ public class SpringMetamodelIndexerBeansTest {
Bean[] beans = springIndex.getBeansWithName("test-spring-indexing", "customerRepository");
assertEquals(1, beans.length);
assertEquals("customerRepository", beans[0].getName());
assertEquals("org.test.springdata.CustomerRepository", beans[0].getType());
assertTrue(beans[0].isTypeCompatibleWith("org.test.springdata.CustomerRepository"));
assertTrue(beans[0].isTypeCompatibleWith("org.springframework.data.repository.CrudRepository"));
Bean repositoryBean = beans[0];
InjectionPoint[] injectionPoints = beans[0].getInjectionPoints();
assertEquals("customerRepository", repositoryBean.getName());
assertEquals("org.test.springdata.CustomerRepository", repositoryBean.getType());
assertTrue(repositoryBean.isTypeCompatibleWith("org.test.springdata.CustomerRepository"));
assertTrue(repositoryBean.isTypeCompatibleWith("org.springframework.data.repository.CrudRepository"));
InjectionPoint[] injectionPoints = repositoryBean.getInjectionPoints();
assertEquals(0, injectionPoints.length);
assertSame(DefaultValues.EMPTY_INJECTION_POINTS, injectionPoints);
}
@@ -235,5 +240,193 @@ public class SpringMetamodelIndexerBeansTest {
assertFalse(beans[0].isTypeCompatibleWith("java.lang.String"));
assertFalse(beans[0].isTypeCompatibleWith("java.util.Comparator"));
}
@Test
void testAnnotationMetadataFromComponentBeans() {
Bean[] beans = springIndex.getBeansWithName("test-spring-indexing", "mainClass");
assertEquals(1, beans.length);
Bean mainClassBean = beans[0];
AnnotationMetadata[] annotations = mainClassBean.getAnnotations();
assertEquals(4, annotations.length);
assertEquals("org.springframework.boot.autoconfigure.SpringBootApplication", annotations[0].getAnnotationType());
assertFalse(annotations[0].isMetaAnnotation());
assertEquals("org.springframework.boot.SpringBootConfiguration", annotations[1].getAnnotationType());
assertTrue(annotations[1].isMetaAnnotation());
assertEquals("org.springframework.context.annotation.Configuration", annotations[2].getAnnotationType());
assertTrue(annotations[2].isMetaAnnotation());
assertEquals("org.springframework.stereotype.Component", annotations[3].getAnnotationType());
assertTrue(annotations[3].isMetaAnnotation());
}
@Test
void testAnnotationMetadataFromBeanMethodBean() {
Bean[] beans = springIndex.getBeansWithName("test-spring-indexing", "bean3");
assertEquals(1, beans.length);
Bean mainClassBean = beans[0];
AnnotationMetadata[] annotations = mainClassBean.getAnnotations();
assertEquals(3, annotations.length);
AnnotationMetadata beanAnnotation = annotations[0];
assertEquals("org.springframework.context.annotation.Bean", beanAnnotation.getAnnotationType());
assertFalse(annotations[0].isMetaAnnotation());
assertEquals(0, annotations[0].getAttributes().size());
AnnotationMetadata qualifierAnnotation = annotations[1];
assertEquals("org.springframework.beans.factory.annotation.Qualifier", qualifierAnnotation.getAnnotationType());
Map<String, String[]> attributes = qualifierAnnotation.getAttributes();
assertEquals(1, attributes.size());
assertTrue(attributes.containsKey("value"));
assertArrayEquals(new String[] {"qualifier1"}, attributes.get("value"));
AnnotationMetadata profileAnnotation = annotations[2];
assertEquals("org.springframework.context.annotation.Profile", profileAnnotation.getAnnotationType());
assertFalse(profileAnnotation.isMetaAnnotation());
attributes = profileAnnotation.getAttributes();
assertEquals(1, attributes.size());
assertTrue(attributes.containsKey("value"));
assertArrayEquals(new String[] {"testprofile","testprofile2"}, attributes.get("value"));
}
@Test
void testAnnotationMetadataFromBeanMethodWithInjectionPointAnnotations() {
Bean[] beans = springIndex.getBeansWithName("test-spring-indexing", "beanWithAnnotationsOnInjectionPoints");
assertEquals(1, beans.length);
Bean bean = beans[0];
AnnotationMetadata[] annotations = bean.getAnnotations();
assertEquals(2, annotations.length);
AnnotationMetadata beanAnnotation = annotations[0];
AnnotationMetadata dependsonAnnotation = annotations[1];
assertEquals("org.springframework.context.annotation.Bean", beanAnnotation.getAnnotationType());
assertEquals("org.springframework.context.annotation.DependsOn", dependsonAnnotation.getAnnotationType());
Map<String, String[]> dependsOnAttributes = dependsonAnnotation.getAttributes();
assertEquals(1, dependsOnAttributes.size());
assertArrayEquals(new String[] {"bean1", "bean2"}, dependsOnAttributes.get("value"));
InjectionPoint[] injectionPoints = bean.getInjectionPoints();
assertEquals(2, injectionPoints.length);
AnnotationMetadata[] annotationsFromPoint1 = injectionPoints[0].getAnnotations();
AnnotationMetadata[] annotationsFromPoint2 = injectionPoints[1].getAnnotations();
assertEquals(1, annotationsFromPoint1.length);
assertEquals(1, annotationsFromPoint2.length);
assertEquals("org.springframework.beans.factory.annotation.Qualifier", annotationsFromPoint1[0].getAnnotationType());
Map<String, String[]> attributesFromParam1 = annotationsFromPoint1[0].getAttributes();
assertEquals(1, attributesFromParam1.size());
assertArrayEquals(new String[] {"q1"}, attributesFromParam1.get("value"));
assertEquals("org.springframework.beans.factory.annotation.Qualifier", annotationsFromPoint2[0].getAnnotationType());
Map<String, String[]> attributesFromParam2 = annotationsFromPoint2[0].getAttributes();
assertEquals(1, attributesFromParam2.size());
assertArrayEquals(new String[] {"q2"}, attributesFromParam2.get("value"));
}
@Test
void testAnnotationMetadataFromComponentClass() {
Bean[] beans = springIndex.getBeansWithName("test-spring-indexing", "configurationWithInjectionsAndAnnotations");
assertEquals(1, beans.length);
Bean bean = beans[0];
InjectionPoint[] injectionPoints = bean.getInjectionPoints();
assertEquals(0, injectionPoints.length);
AnnotationMetadata[] annotations = bean.getAnnotations();
assertEquals(4, annotations.length);
AnnotationMetadata configurationAnnotation= annotations[0];
AnnotationMetadata qualifierAnnotation = annotations[1];
AnnotationMetadata runtimeHintsAnnotation = annotations[2];
AnnotationMetadata componentMetaAnnotation = annotations[3];
assertEquals("org.springframework.context.annotation.Configuration", configurationAnnotation.getAnnotationType());
assertEquals("org.springframework.beans.factory.annotation.Qualifier", qualifierAnnotation.getAnnotationType());
assertEquals("org.springframework.context.annotation.ImportRuntimeHints", runtimeHintsAnnotation.getAnnotationType());
assertEquals("org.springframework.stereotype.Component", componentMetaAnnotation.getAnnotationType());
Map<String, String[]> qualifierAttributes = qualifierAnnotation.getAttributes();
assertEquals(1, qualifierAttributes.size());
assertArrayEquals(new String[] {"qualifier"}, qualifierAttributes.get("value"));
Map<String, String[]> runtimeHintsAttributes = runtimeHintsAnnotation.getAttributes();
assertEquals(1, runtimeHintsAttributes.size());
assertArrayEquals(new String[] {"org.test.MainClass"}, runtimeHintsAttributes.get("value"));
}
@Test
void testAnnotationMetadataFromInjectionPointsFromAutowiredFields() {
Bean[] beans = springIndex.getBeansWithName("test-spring-indexing", "autowiredInjectionService");
assertEquals(1, beans.length);
InjectionPoint[] injectionPoints = beans[0].getInjectionPoints();
assertEquals(2, injectionPoints.length);
AnnotationMetadata[] annotationsPoint1 = injectionPoints[0].getAnnotations();
assertEquals(1, annotationsPoint1.length);
assertEquals("org.springframework.beans.factory.annotation.Autowired", annotationsPoint1[0].getAnnotationType());
assertFalse(annotationsPoint1[0].isMetaAnnotation());
assertEquals(0, annotationsPoint1[0].getAttributes().size());
AnnotationMetadata[] annotationsPoint2 = injectionPoints[1].getAnnotations();
assertEquals(2, annotationsPoint2.length);
AnnotationMetadata autowiredFromPoint2 = annotationsPoint2[0];
assertEquals("org.springframework.beans.factory.annotation.Autowired", autowiredFromPoint2.getAnnotationType());
assertFalse(autowiredFromPoint2.isMetaAnnotation());
assertEquals(0, autowiredFromPoint2.getAttributes().size());
AnnotationMetadata qualifierFromPoint2 = annotationsPoint2[1];
assertEquals("org.springframework.beans.factory.annotation.Qualifier", qualifierFromPoint2.getAnnotationType());
assertFalse(qualifierFromPoint2.isMetaAnnotation());
assertEquals(1, qualifierFromPoint2.getAttributes().size());
Map<String, String[]> qualifierAttributes = qualifierFromPoint2.getAttributes();
assertEquals(1, qualifierAttributes.size());
assertArrayEquals(new String[] {"qual1"}, qualifierAttributes.get("value"));
}
@Test
void testAnnotationMetadataFromSpringDataRepository() {
Bean[] beans = springIndex.getBeansWithName("test-spring-indexing", "customerRepository");
assertEquals(1, beans.length);
AnnotationMetadata[] annotations = beans[0].getAnnotations();
assertEquals(2, annotations.length);
AnnotationMetadata qualifierAnnotation = annotations[0];
AnnotationMetadata profileAnnotation = annotations[1];
assertEquals("org.springframework.beans.factory.annotation.Qualifier", qualifierAnnotation.getAnnotationType());
assertFalse(qualifierAnnotation.isMetaAnnotation());
Map<String, String[]> qualifierAttributes = qualifierAnnotation.getAttributes();
assertEquals(1, qualifierAttributes.size());
assertArrayEquals(new String[] {"repoQualifier"}, qualifierAttributes.get("value"));
assertEquals("org.springframework.context.annotation.Profile", profileAnnotation.getAnnotationType());
assertFalse(profileAnnotation.isMetaAnnotation());
Map<String, String[]> profileAttributes = profileAnnotation.getAttributes();
assertEquals(1, profileAttributes.size());
assertArrayEquals(new String[] {"prof1", "prof2"}, profileAttributes.get("value"));
}
}

View File

@@ -165,7 +165,7 @@ public class DependsOnCompletionProviderTest {
assertCompletions("@DependsOn({\"bean1\",<*>\"bean2\"})", 1, "@DependsOn({\"bean1\",\"bean3\",<*>\"bean2\"})");
}
private void assertCompletions(String completionLine, int noOfExcpectedCompletions, String expectedCompletedLine) throws Exception {
private void assertCompletions(String completionLine, int noOfExpectedCompletions, String expectedCompletedLine) throws Exception {
String editorContent = """
package org.test;
@@ -182,9 +182,9 @@ public class DependsOnCompletionProviderTest {
Editor editor = harness.newEditor(LanguageId.JAVA, editorContent, tempJavaDocUri);
List<CompletionItem> completions = editor.getCompletions();
assertEquals(noOfExcpectedCompletions, completions.size());
assertEquals(noOfExpectedCompletions, completions.size());
if (noOfExcpectedCompletions > 0) {
if (noOfExpectedCompletions > 0) {
editor.apply(completions.get(0));
assertEquals("""
package org.test;

View File

@@ -0,0 +1,222 @@
/*******************************************************************************
* 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.test;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Import;
import org.springframework.ide.vscode.boot.app.SpringSymbolIndex;
import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest;
import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf;
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.protocol.spring.AnnotationMetadata;
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
import org.springframework.ide.vscode.commons.util.text.LanguageId;
import org.springframework.ide.vscode.languageserver.testharness.Editor;
import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness;
import org.springframework.ide.vscode.project.harness.ProjectsHarness;
import org.springframework.test.context.junit.jupiter.SpringExtension;
/**
* @author Martin Lippert
*/
@ExtendWith(SpringExtension.class)
@BootLanguageServerTest
@Import(SymbolProviderTestConf.class)
public class QualifierCompletionProviderTest {
@Autowired private BootLanguageServerHarness harness;
@Autowired private JavaProjectFinder projectFinder;
@Autowired private SpringMetamodelIndex springIndex;
@Autowired private SpringSymbolIndex indexer;
private File directory;
private IJavaProject project;
private Bean[] indexedBeans;
private String tempJavaDocUri;
private Bean bean1;
private Bean bean2;
@BeforeEach
public void setup() throws Exception {
harness.intialize(null);
directory = new File(ProjectsHarness.class.getResource("/test-projects/test-spring-indexing/").toURI());
String projectDir = directory.toURI().toString();
project = projectFinder.find(new TextDocumentIdentifier(projectDir)).get();
CompletableFuture<Void> initProject = indexer.waitOperation();
initProject.get(5, TimeUnit.SECONDS);
indexedBeans = springIndex.getBeansOfProject(project.getElementName());
tempJavaDocUri = directory.toPath().resolve("src/main/java/org/test/TempClass.java").toUri().toString();
AnnotationMetadata annotationBean1 = new AnnotationMetadata("org.springframework.beans.factory.annotation.Qualifier", false, Map.of("value", new String[] {"quali1"}));
AnnotationMetadata annotationBean2 = new AnnotationMetadata("org.springframework.beans.factory.annotation.Qualifier", false, Map.of("value", new String[] {"quali2"}));
bean1 = new Bean("bean1", "type1", new Location(tempJavaDocUri, new Range(new Position(1,1), new Position(1, 20))), null, null, new AnnotationMetadata[] {annotationBean1});
bean2 = new Bean("bean2", "type2", new Location(tempJavaDocUri, new Range(new Position(1,1), new Position(1, 20))), null, null, new AnnotationMetadata[] {annotationBean2});
springIndex.updateBeans(project.getElementName(), new Bean[] {bean1, bean2});
}
@AfterEach
public void restoreIndexState() {
this.springIndex.updateBeans(project.getElementName(), indexedBeans);
}
@Test
public void testQualifierCompletionWithoutQuotesWithoutPrefix() throws Exception {
assertCompletions("@Qualifier(<*>)", 4, new String[] {"bean1", "bean2", "quali1", "quali2"}, 0, "@Qualifier(\"bean1\"<*>)");
}
// @Test
// public void testDependsOnCompletionWithoutQuotesWithPrefix() throws Exception {
// assertCompletions("@DependsOn(be<*>)", 2, "@DependsOn(\"bean1\"<*>)");
// }
//
// @Test
// public void testDependsOnCompletionWithoutQuotesWithAttributeName() throws Exception {
// assertCompletions("@DependsOn(value=<*>)", 2, "@DependsOn(value=\"bean1\"<*>)");
// }
//
// @Test
// public void testDependsOnCompletionInsideOfQuotesWithoutPrefix() throws Exception {
// assertCompletions("@DependsOn(\"<*>\")", 2, "@DependsOn(\"bean1<*>\")");
// }
//
// @Test
// public void testDependsOnCompletionWithoutQuotesWithoutPrefixInsideArray() throws Exception {
// assertCompletions("@DependsOn({<*>})", 2, "@DependsOn({\"bean1\"<*>})");
// }
//
// @Test
// public void testDependsOnCompletionInsideOfQuotesWithoutPrefixInsideArray() throws Exception {
// assertCompletions("@DependsOn({\"<*>\"})", 2, "@DependsOn({\"bean1<*>\"})");
// }
//
// @Test
// public void testDependsOnCompletionInsideOfQuotesWithPrefix() throws Exception {
// assertCompletions("@DependsOn(\"be<*>\")", 2, "@DependsOn(\"bean1<*>\")");
// }
//
// @Test
// public void testDependsOnCompletionInsideOfQuotesAndArrayWithPrefix() throws Exception {
// assertCompletions("@DependsOn({\"be<*>\"})", 2, "@DependsOn({\"bean1<*>\"})");
// }
//
// @Test
// public void testDependsOnCompletionInsideOfQuotesWithPrefixButWithoutMatches() throws Exception {
// assertCompletions("@DependsOn(\"XXX<*>\")", 0, null);
// }
//
// @Test
// public void testDependsOnCompletionOutsideOfAnnotation1() throws Exception {
// assertCompletions("@DependsOn(\"XXX\")<*>", 0, null);
// }
//
// @Test
// public void testDependsOnCompletionOutsideOfAnnotation2() throws Exception {
// assertCompletions("@DependsOn<*>(\"XXX\")", 0, null);
// }
//
// @Test
// public void testDependsOnCompletionInsideOfQuotesWithPrefixAndReplacedPostfix() throws Exception {
// assertCompletions("@DependsOn(\"be<*>xxx\")", 2, "@DependsOn(\"bean1<*>xxx\")");
// }
//
// @Test
// public void testDependsOnCompletionInsideOfArrayBehindExistingElement() throws Exception {
// assertCompletions("@DependsOn({\"bean1\",<*>})", 1, "@DependsOn({\"bean1\",\"bean2\"<*>})");
// }
//
// @Test
// public void testDependsOnCompletionInsideOfArrayInFrontOfExistingElement() throws Exception {
// assertCompletions("@DependsOn({<*>\"bean1\"})", 1, "@DependsOn({\"bean2\",<*>\"bean1\"})");
// }
//
// @Test
// public void testDependsOnCompletionInsideOfArrayBetweenExistingElements() throws Exception {
// Bean bean3 = new Bean("bean3", "type3", new Location(tempJavaDocUri, new Range(new Position(1,1), new Position(1, 20))), null, null, null);
// springIndex.updateBeans(project.getElementName(), new Bean[] {bean1, bean2, bean3});
//
// assertCompletions("@DependsOn({\"bean1\",<*>\"bean2\"})", 1, "@DependsOn({\"bean1\",\"bean3\",<*>\"bean2\"})");
// }
private void assertCompletions(String completionLine, int noOfExpectedCompletions, String expectedCompletedLine) throws Exception {
assertCompletions(completionLine, noOfExpectedCompletions, null, 0, expectedCompletedLine);
}
private void assertCompletions(String completionLine, int noOfExcpectedCompletions, String[] expectedCompletions, int chosenCompletion, String expectedCompletedLine) throws Exception {
String editorContent = """
package org.test;
import org.springframework.beans.factory.annotation.Qualifier;
@Component
""" +
completionLine + "\n" +
"""
public class TestDependsOnClass {
}
""";
Editor editor = harness.newEditor(LanguageId.JAVA, editorContent, tempJavaDocUri);
List<CompletionItem> completions = editor.getCompletions();
assertEquals(noOfExcpectedCompletions, completions.size());
if (expectedCompletions != null) {
String[] completionItems = completions.stream()
.map(item -> item.getLabel())
.toArray(size -> new String[size]);
assertArrayEquals(expectedCompletions, completionItems);
}
if (noOfExcpectedCompletions > 0) {
editor.apply(completions.get(chosenCompletion));
assertEquals("""
package org.test;
import org.springframework.beans.factory.annotation.Qualifier;
@Component
""" + expectedCompletedLine + "\n" +
"""
public class TestDependsOnClass {
}
""", editor.getText());
}
}
}

View File

@@ -0,0 +1,155 @@
/*******************************************************************************
* 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.test;
import static org.junit.Assert.assertEquals;
import java.io.File;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.lsp4j.LocationLink;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Import;
import org.springframework.ide.vscode.boot.app.SpringSymbolIndex;
import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest;
import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf;
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
import org.springframework.ide.vscode.commons.java.IJavaProject;
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.text.LanguageId;
import org.springframework.ide.vscode.languageserver.testharness.Editor;
import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness;
import org.springframework.ide.vscode.project.harness.ProjectsHarness;
import org.springframework.test.context.junit.jupiter.SpringExtension;
/**
* @author Martin Lippert
*/
@ExtendWith(SpringExtension.class)
@BootLanguageServerTest
@Import(SymbolProviderTestConf.class)
public class QualifierDefinitionProviderTest {
@Autowired private BootLanguageServerHarness harness;
@Autowired private JavaProjectFinder projectFinder;
@Autowired private SpringMetamodelIndex springIndex;
@Autowired private SpringSymbolIndex indexer;
private File directory;
private IJavaProject project;
@BeforeEach
public void setup() throws Exception {
harness.intialize(null);
directory = new File(ProjectsHarness.class.getResource("/test-projects/test-spring-indexing/").toURI());
String projectDir = directory.toURI().toString();
project = projectFinder.find(new TextDocumentIdentifier(projectDir)).get();
CompletableFuture<Void> initProject = indexer.waitOperation();
initProject.get(5, TimeUnit.SECONDS);
}
@Test
public void testQualifierRefersToBeanDefinitionLink() throws Exception {
String tempJavaDocUri = directory.toPath().resolve("src/main/java/org/test/TempClass.java").toUri().toString();
Editor editor = harness.newEditor(LanguageId.JAVA, """
package org.test;
import org.springframework.beans.factory.annotation.Qualifier;
@Component
@Qualifier("bean1")
public class TestDependsOnClass {
}""", tempJavaDocUri);
String expectedDefinitionUri = directory.toPath().resolve("src/main/java/org/test/MainClass.java").toUri().toString();
Bean[] beans = springIndex.getBeansWithName(project.getElementName(), "bean1");
assertEquals(1, beans.length);
LocationLink expectedLocation = new LocationLink(expectedDefinitionUri,
beans[0].getLocation().getRange(), beans[0].getLocation().getRange(),
null);
editor.assertLinkTargets("bean1", List.of(expectedLocation));
}
// @Test
// public void testMultipleDependsOnBeanDefinitionLink() throws Exception {
// String tempJavaDocUri = directory.toPath().resolve("src/main/java/org/test/TempClass.java").toUri().toString();
//
// Editor editor = harness.newEditor(LanguageId.JAVA, """
// package org.test;
//
// import org.springframework.context.annotation.DependsOn;
//
// @Component
// @DependsOn({"bean1", "bean2"})
// public class TestDependsOnClass {
// }""", tempJavaDocUri);
//
// String expectedDefinitionUri = directory.toPath().resolve("src/main/java/org/test/MainClass.java").toUri().toString();
//
// Bean[] beans = springIndex.getBeansWithName(project.getElementName(), "bean1");
// assertEquals(1, beans.length);
//
// LocationLink expectedLocation = new LocationLink(expectedDefinitionUri,
// beans[0].getLocation().getRange(), beans[0].getLocation().getRange(),
// null);
//
// editor.assertLinkTargets("bean1", List.of(expectedLocation));
// }
//
// @Test
// public void testDependsOnWithMultipleBeanDefinitionLinks() throws Exception {
// String tempJavaDocUri = directory.toPath().resolve("src/main/java/org/test/TempClass.java").toUri().toString();
//
// Editor editor = harness.newEditor(LanguageId.JAVA, """
// package org.test;
//
// import org.springframework.context.annotation.DependsOn;
//
// @Component
// @DependsOn("bean1")
// public class TestDependsOnClass {
// }""", tempJavaDocUri);
//
// String expectedDefinitionUri = directory.toPath().resolve("src/main/java/org/test/MainClass.java").toUri().toString();
//
// List<Bean> beansOfDoc = new ArrayList<>(List.of(springIndex.getBeansOfDocument(expectedDefinitionUri)));
// beansOfDoc.add(new Bean("bean1", "type", new Location(expectedDefinitionUri, new Range(new Position(20, 1), new Position(20, 10))), null, null, null));
// springIndex.updateBeans(project.getElementName(), expectedDefinitionUri, beansOfDoc.toArray(new Bean[0]));
//
// Bean[] beans = springIndex.getBeansWithName(project.getElementName(), "bean1");
// assertEquals(2, beans.length);
//
// LocationLink expectedLocation1 = new LocationLink(expectedDefinitionUri,
// beans[0].getLocation().getRange(), beans[0].getLocation().getRange(),
// null);
//
// LocationLink expectedLocation2 = new LocationLink(expectedDefinitionUri,
// beans[1].getLocation().getRange(), beans[1].getLocation().getRange(),
// null);
//
// editor.assertLinkTargets("bean1", List.of(expectedLocation1, expectedLocation2));
// }
}

View File

@@ -3,6 +3,8 @@ package org.test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Profile;
import org.springframework.beans.factory.annotation.Qualifier;
@SpringBootApplication
public class MainClass {
@@ -21,4 +23,11 @@ public class MainClass {
return new BeanClass2();
}
@Bean
@Qualifier("qualifier1")
@Profile({"testprofile","testprofile2"})
BeanClass2 bean3() {
return new BeanClass2();
}
}

View File

@@ -1,6 +1,7 @@
package org.test.injections;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.test.BeanClass1;
import org.test.BeanClass2;
@@ -8,8 +9,12 @@ import org.test.BeanClass2;
@Service
public class AutowiredInjectionService {
@Autowired private BeanClass1 bean1;
@Autowired private BeanClass2 bean2;
@Autowired
private BeanClass1 bean1;
@Autowired
@Qualifier("qual1")
private BeanClass2 bean2;
public BeanClass1 getBean1() {
return bean1;

View File

@@ -0,0 +1,25 @@
package org.test.injections;
import org.test.MainClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.ImportRuntimeHints;
import org.springframework.beans.factory.annotation.Qualifier;
import org.test.BeanClass1;
import org.test.BeanClass2;
import org.test.ManuallyCreatedBeanWithConstructor;
@Configuration
@Qualifier("qualifier")
@ImportRuntimeHints(MainClass.class)
public class ConfigurationWithInjectionsAndAnnotations {
@Bean
@DependsOn({"bean1", "bean2"})
ManuallyCreatedBeanWithConstructor beanWithAnnotationsOnInjectionPoints(@Qualifier("q1") BeanClass1 bean1, @Qualifier("q2") BeanClass2 bean2) {
return new ManuallyCreatedBeanWithConstructor(bean1, bean2);
}
}

View File

@@ -2,9 +2,14 @@ package org.test.springdata;
import java.util.List;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Profile;
import org.springframework.data.repository.CrudRepository;
@Qualifier("repoQualifier")
@Profile({"prof1", "prof2"})
public interface CustomerRepository extends CrudRepository<Customer, Long> {
List<Customer> findByLastName(String lastName);
}