GH-1494: record types now supported in configuration properties indexing
This commit is contained in:
@@ -14,6 +14,7 @@ import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.Expression;
|
||||
import org.eclipse.jdt.core.dom.MethodDeclaration;
|
||||
@@ -33,7 +34,7 @@ public class BeanUtils {
|
||||
|
||||
private static final String[] NAME_ATTRIBUTES = {"value", "name"};
|
||||
|
||||
public static String getBeanNameFromComponentAnnotation(Annotation annotation, TypeDeclaration type) {
|
||||
public static String getBeanNameFromComponentAnnotation(Annotation annotation, AbstractTypeDeclaration type) {
|
||||
Optional<Expression> attribute = ASTUtils.getAttribute(annotation, "value");
|
||||
if (attribute.isPresent()) {
|
||||
return ASTUtils.getExpressionValueAsString(attribute.get(), (a) -> {});
|
||||
|
||||
@@ -19,12 +19,14 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTVisitor;
|
||||
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.Expression;
|
||||
import org.eclipse.jdt.core.dom.IMethodBinding;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.MethodDeclaration;
|
||||
import org.eclipse.jdt.core.dom.MethodInvocation;
|
||||
import org.eclipse.jdt.core.dom.RecordDeclaration;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
import org.eclipse.lsp4j.Location;
|
||||
import org.eclipse.lsp4j.SymbolKind;
|
||||
@@ -46,6 +48,7 @@ import org.springframework.ide.vscode.boot.java.utils.DefaultSymbolProvider;
|
||||
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.DefaultValues;
|
||||
import org.springframework.ide.vscode.commons.protocol.spring.InjectionPoint;
|
||||
import org.springframework.ide.vscode.commons.protocol.spring.SimpleSymbolElement;
|
||||
import org.springframework.ide.vscode.commons.util.BadLocationException;
|
||||
@@ -63,8 +66,11 @@ public class ComponentSymbolProvider implements SymbolProvider {
|
||||
@Override
|
||||
public void addSymbols(Annotation node, ITypeBinding annotationType, Collection<ITypeBinding> metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) {
|
||||
try {
|
||||
if (node != null && node.getParent() != null && node.getParent() instanceof TypeDeclaration) {
|
||||
createSymbol(node, annotationType, metaAnnotations, context, doc);
|
||||
if (node != null && node.getParent() != null && node.getParent() instanceof TypeDeclaration type) {
|
||||
createSymbol(type, node, annotationType, metaAnnotations, context, doc);
|
||||
}
|
||||
else if (node != null && node.getParent() != null && node.getParent() instanceof RecordDeclaration record) {
|
||||
createSymbol(record, node, annotationType, metaAnnotations, context, doc);
|
||||
}
|
||||
else if (Annotations.NAMED_ANNOTATIONS.contains(annotationType.getQualifiedName())) {
|
||||
WorkspaceSymbol symbol = DefaultSymbolProvider.provideDefaultSymbol(node, doc);
|
||||
@@ -77,15 +83,13 @@ public class ComponentSymbolProvider implements SymbolProvider {
|
||||
}
|
||||
}
|
||||
|
||||
protected void createSymbol(Annotation node, ITypeBinding annotationType, Collection<ITypeBinding> metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) throws BadLocationException {
|
||||
private void createSymbol(TypeDeclaration type, Annotation node, ITypeBinding annotationType, Collection<ITypeBinding> metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) throws BadLocationException {
|
||||
String annotationTypeName = annotationType.getName();
|
||||
|
||||
Collection<String> metaAnnotationNames = metaAnnotations.stream()
|
||||
.map(ITypeBinding::getName)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
TypeDeclaration type = (TypeDeclaration) node.getParent();
|
||||
|
||||
String beanName = BeanUtils.getBeanNameFromComponentAnnotation(node, type);
|
||||
ITypeBinding beanType = type.resolveBinding();
|
||||
|
||||
@@ -141,7 +145,48 @@ public class ComponentSymbolProvider implements SymbolProvider {
|
||||
context.getBeans().add(new CachedBean(context.getDocURI(), beanDefinition));
|
||||
}
|
||||
|
||||
private void indexConfigurationProperties(Bean beanDefinition, TypeDeclaration type, SpringIndexerJavaContext context, TextDocument doc) {
|
||||
private void createSymbol(RecordDeclaration record, Annotation node, ITypeBinding annotationType, Collection<ITypeBinding> metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) throws BadLocationException {
|
||||
String annotationTypeName = annotationType.getName();
|
||||
|
||||
Collection<String> metaAnnotationNames = metaAnnotations.stream()
|
||||
.map(ITypeBinding::getName)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
String beanName = BeanUtils.getBeanNameFromComponentAnnotation(node, record);
|
||||
ITypeBinding beanType = record.resolveBinding();
|
||||
|
||||
Location location = new Location(doc.getUri(), doc.toRange(node.getStartPosition(), node.getLength()));
|
||||
|
||||
WorkspaceSymbol symbol = new WorkspaceSymbol(
|
||||
beanLabel("+", annotationTypeName, metaAnnotationNames, beanName, beanType.getName()), SymbolKind.Interface,
|
||||
Either.forLeft(location));
|
||||
|
||||
boolean isConfiguration = Annotations.CONFIGURATION.equals(annotationType.getQualifiedName())
|
||||
|| metaAnnotations.stream().anyMatch(t -> Annotations.CONFIGURATION.equals(t.getQualifiedName()));
|
||||
|
||||
InjectionPoint[] injectionPoints = DefaultValues.EMPTY_INJECTION_POINTS;
|
||||
|
||||
Set<String> supertypes = new HashSet<>();
|
||||
ASTUtils.findSupertypes(beanType, supertypes);
|
||||
|
||||
Collection<Annotation> annotationsOnType = ASTUtils.getAnnotations(record);
|
||||
|
||||
AnnotationMetadata[] annotations = Stream.concat(
|
||||
Arrays.stream(ASTUtils.getAnnotationsMetadata(annotationsOnType, doc))
|
||||
,
|
||||
metaAnnotations.stream()
|
||||
.map(an -> new AnnotationMetadata(an.getQualifiedName(), true, null, null)))
|
||||
.toArray(AnnotationMetadata[]::new);
|
||||
|
||||
Bean beanDefinition = new Bean(beanName, beanType.getQualifiedName(), location, injectionPoints, supertypes, annotations, isConfiguration, symbol.getName());
|
||||
|
||||
indexConfigurationProperties(beanDefinition, record, context, doc);
|
||||
|
||||
context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), symbol));
|
||||
context.getBeans().add(new CachedBean(context.getDocURI(), beanDefinition));
|
||||
}
|
||||
|
||||
private void indexConfigurationProperties(Bean beanDefinition, AbstractTypeDeclaration type, SpringIndexerJavaContext context, TextDocument doc) {
|
||||
AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(type);
|
||||
|
||||
if (annotationHierarchies.isAnnotatedWith(type.resolveBinding(), Annotations.CONFIGURATION_PROPERTIES)) {
|
||||
|
||||
@@ -18,10 +18,13 @@ import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.FieldDeclaration;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.RecordDeclaration;
|
||||
import org.eclipse.jdt.core.dom.SimpleName;
|
||||
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
|
||||
import org.eclipse.jdt.core.dom.Type;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
|
||||
@@ -55,8 +58,10 @@ public class ConfigurationPropertiesSymbolProvider implements SymbolProvider {
|
||||
@Override
|
||||
public void addSymbols(Annotation node, ITypeBinding annotationType, Collection<ITypeBinding> metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) {
|
||||
try {
|
||||
if (node != null && node.getParent() != null && node.getParent() instanceof TypeDeclaration) {
|
||||
createSymbol(node, annotationType, metaAnnotations, context, doc);
|
||||
if (node != null && node.getParent() != null) {
|
||||
if (node.getParent() instanceof AbstractTypeDeclaration abstractType) {
|
||||
createSymbolForType(abstractType, node, annotationType, metaAnnotations, context, doc);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (BadLocationException e) {
|
||||
@@ -64,14 +69,13 @@ public class ConfigurationPropertiesSymbolProvider implements SymbolProvider {
|
||||
}
|
||||
}
|
||||
|
||||
protected void createSymbol(Annotation node, ITypeBinding annotationType, Collection<ITypeBinding> metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) throws BadLocationException {
|
||||
protected void createSymbolForType(AbstractTypeDeclaration type, Annotation node, ITypeBinding annotationType, Collection<ITypeBinding> metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) throws BadLocationException {
|
||||
String annotationTypeName = annotationType.getName();
|
||||
|
||||
Collection<String> metaAnnotationNames = metaAnnotations.stream()
|
||||
.map(ITypeBinding::getName)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
TypeDeclaration type = (TypeDeclaration) node.getParent();
|
||||
ITypeBinding typeBinding = type.resolveBinding();
|
||||
|
||||
AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(type);
|
||||
@@ -79,12 +83,11 @@ public class ConfigurationPropertiesSymbolProvider implements SymbolProvider {
|
||||
|
||||
if (!isComponentAnnotated) {
|
||||
String beanName = BeanUtils.getBeanNameFromType(type.getName().getFullyQualifiedName());
|
||||
ITypeBinding beanType = type.resolveBinding();
|
||||
|
||||
Location location = new Location(doc.getUri(), doc.toRange(type.getStartPosition(), type.getLength()));
|
||||
|
||||
WorkspaceSymbol symbol = new WorkspaceSymbol(
|
||||
ComponentSymbolProvider.beanLabel("+", annotationTypeName, metaAnnotationNames, beanName, beanType.getName()), SymbolKind.Interface,
|
||||
ComponentSymbolProvider.beanLabel("+", annotationTypeName, metaAnnotationNames, beanName, typeBinding.getName()), SymbolKind.Interface,
|
||||
Either.forLeft(location));
|
||||
|
||||
boolean isConfiguration = false; // otherwise, the ComponentSymbolProvider takes care of the bean definiton for this type
|
||||
@@ -92,7 +95,7 @@ public class ConfigurationPropertiesSymbolProvider implements SymbolProvider {
|
||||
InjectionPoint[] injectionPoints = ASTUtils.findInjectionPoints(type, doc);
|
||||
|
||||
Set<String> supertypes = new HashSet<>();
|
||||
ASTUtils.findSupertypes(beanType, supertypes);
|
||||
ASTUtils.findSupertypes(typeBinding, supertypes);
|
||||
|
||||
Collection<Annotation> annotationsOnType = ASTUtils.getAnnotations(type);
|
||||
|
||||
@@ -103,7 +106,7 @@ public class ConfigurationPropertiesSymbolProvider implements SymbolProvider {
|
||||
.map(an -> new AnnotationMetadata(an.getQualifiedName(), true, null, null)))
|
||||
.toArray(AnnotationMetadata[]::new);
|
||||
|
||||
Bean beanDefinition = new Bean(beanName, beanType.getQualifiedName(), location, injectionPoints, supertypes, annotations, isConfiguration, symbol.getName());
|
||||
Bean beanDefinition = new Bean(beanName, typeBinding.getQualifiedName(), location, injectionPoints, supertypes, annotations, isConfiguration, symbol.getName());
|
||||
|
||||
indexConfigurationProperties(beanDefinition, type, context, doc);
|
||||
|
||||
@@ -112,7 +115,16 @@ public class ConfigurationPropertiesSymbolProvider implements SymbolProvider {
|
||||
}
|
||||
}
|
||||
|
||||
public static void indexConfigurationProperties(Bean beanDefinition, TypeDeclaration type, SpringIndexerJavaContext context, TextDocument doc) {
|
||||
public static void indexConfigurationProperties(Bean beanDefinition, AbstractTypeDeclaration abstractType, SpringIndexerJavaContext context, TextDocument doc) {
|
||||
if (abstractType instanceof TypeDeclaration type) {
|
||||
indexConfigurationPropertiesForType(beanDefinition, type, context, doc);
|
||||
}
|
||||
else if (abstractType instanceof RecordDeclaration record) {
|
||||
indexConfigurationPropertiesForRecord(beanDefinition, record, context, doc);
|
||||
}
|
||||
}
|
||||
|
||||
public static void indexConfigurationPropertiesForType(Bean beanDefinition, TypeDeclaration type, SpringIndexerJavaContext context, TextDocument doc) {
|
||||
|
||||
FieldDeclaration[] fields = type.getFields();
|
||||
if (fields != null) {
|
||||
@@ -145,4 +157,32 @@ public class ConfigurationPropertiesSymbolProvider implements SymbolProvider {
|
||||
|
||||
}
|
||||
|
||||
public static void indexConfigurationPropertiesForRecord(Bean beanDefinition, RecordDeclaration record, SpringIndexerJavaContext context, TextDocument doc) {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<SingleVariableDeclaration> fields = record.recordComponents();
|
||||
|
||||
if (fields != null) {
|
||||
for (SingleVariableDeclaration field : fields) {
|
||||
try {
|
||||
Type fieldType = field.getType();
|
||||
if (fieldType != null) {
|
||||
|
||||
SimpleName name = field.getName();
|
||||
if (name != null) {
|
||||
|
||||
DocumentRegion nodeRegion = ASTUtils.nodeRegion(doc, field);
|
||||
Range range = doc.toRange(nodeRegion);
|
||||
ConfigPropertyIndexElement configPropElement = new ConfigPropertyIndexElement(name.getFullyQualifiedName(), fieldType.resolveBinding().getQualifiedName(), range);
|
||||
|
||||
beanDefinition.addChild(configPropElement);
|
||||
}
|
||||
}
|
||||
} catch (BadLocationException e) {
|
||||
log.error("error identifying config property field", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.ArrayInitializer;
|
||||
import org.eclipse.jdt.core.dom.BodyDeclaration;
|
||||
@@ -39,6 +40,7 @@ import org.eclipse.jdt.core.dom.Modifier;
|
||||
import org.eclipse.jdt.core.dom.Name;
|
||||
import org.eclipse.jdt.core.dom.NormalAnnotation;
|
||||
import org.eclipse.jdt.core.dom.QualifiedName;
|
||||
import org.eclipse.jdt.core.dom.RecordDeclaration;
|
||||
import org.eclipse.jdt.core.dom.SimpleName;
|
||||
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
|
||||
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
|
||||
@@ -284,10 +286,26 @@ public class ASTUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static Collection<Annotation> getAnnotations(AbstractTypeDeclaration abstractTypeDeclaration) {
|
||||
if (abstractTypeDeclaration instanceof TypeDeclaration typeDeclaration) {
|
||||
return getAnnotations(typeDeclaration);
|
||||
}
|
||||
else if (abstractTypeDeclaration instanceof RecordDeclaration recordDeclaration) {
|
||||
return getAnnotations(recordDeclaration);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Collection<Annotation> getAnnotations(TypeDeclaration typeDeclaration) {
|
||||
return getAnnotationsFromModifiers(typeDeclaration.getStructuralProperty(TypeDeclaration.MODIFIERS2_PROPERTY));
|
||||
}
|
||||
|
||||
public static Collection<Annotation> getAnnotations(RecordDeclaration recordDeclaration) {
|
||||
return getAnnotationsFromModifiers(recordDeclaration.getStructuralProperty(RecordDeclaration.MODIFIERS2_PROPERTY));
|
||||
}
|
||||
|
||||
public static Collection<Annotation> getAnnotations(MethodDeclaration methodDeclaration) {
|
||||
return getAnnotationsFromModifiers(methodDeclaration.getStructuralProperty(MethodDeclaration.MODIFIERS2_PROPERTY));
|
||||
}
|
||||
@@ -488,7 +506,16 @@ public class ASTUtils {
|
||||
return result.size() > 0 ? result.toArray(new InjectionPoint[result.size()]) : DefaultValues.EMPTY_INJECTION_POINTS;
|
||||
}
|
||||
|
||||
public static InjectionPoint[] findInjectionPoints(TypeDeclaration type, TextDocument doc) throws BadLocationException {
|
||||
public static InjectionPoint[] findInjectionPoints(AbstractTypeDeclaration abstractType, TextDocument doc) throws BadLocationException {
|
||||
if (abstractType instanceof TypeDeclaration type) {
|
||||
return findInjectionPointsForType(type, doc);
|
||||
}
|
||||
else {
|
||||
return DefaultValues.EMPTY_INJECTION_POINTS;
|
||||
}
|
||||
}
|
||||
|
||||
public static InjectionPoint[] findInjectionPointsForType(TypeDeclaration type, TextDocument doc) throws BadLocationException {
|
||||
List<InjectionPoint> result = new ArrayList<>();
|
||||
|
||||
findInjectionPoints(type.getMethods(), doc, result);
|
||||
|
||||
@@ -102,4 +102,51 @@ public class SpringIndexerConfigurationPropertiesTest {
|
||||
assertEquals("java.lang.String", configPropElement.getType());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSimpleConfigPropertiesRecord() throws Exception {
|
||||
String docUri = directory.toPath().resolve("src/main/java/com/example/configproperties/ConfigurationPropertiesWithRecords.java").toUri().toString();
|
||||
|
||||
Bean[] beans = springIndex.getBeansOfDocument(docUri);
|
||||
assertEquals(1, beans.length);
|
||||
|
||||
Bean configPropertiesComponentBean = Arrays.stream(beans).filter(bean -> bean.getName().equals("configurationPropertiesWithRecords")).findFirst().get();
|
||||
assertEquals("com.example.configproperties.ConfigurationPropertiesWithRecords", configPropertiesComponentBean.getType());
|
||||
|
||||
List<SpringIndexElement> children = configPropertiesComponentBean.getChildren();
|
||||
assertEquals(2, children.size());
|
||||
|
||||
ConfigPropertyIndexElement configPropElement1 = (ConfigPropertyIndexElement) children.get(0);
|
||||
assertEquals("name", configPropElement1.getName());
|
||||
assertEquals("java.lang.String", configPropElement1.getType());
|
||||
|
||||
ConfigPropertyIndexElement configPropElement2 = (ConfigPropertyIndexElement) children.get(1);
|
||||
assertEquals("duration", configPropElement2.getName());
|
||||
assertEquals("int", configPropElement2.getType());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSimpleConfigPropertiesRecordAndConfigurationAnnotation() throws Exception {
|
||||
String docUri = directory.toPath().resolve("src/main/java/com/example/configproperties/ConfigurationPropertiesWithRecordsAndConfigurationAnnotation.java").toUri().toString();
|
||||
|
||||
Bean[] beans = springIndex.getBeansOfDocument(docUri);
|
||||
assertEquals(1, beans.length);
|
||||
|
||||
Bean configPropertiesComponentBean = Arrays.stream(beans).filter(bean -> bean.getName().equals("configurationPropertiesWithRecordsAndConfigurationAnnotation")).findFirst().get();
|
||||
assertEquals("com.example.configproperties.ConfigurationPropertiesWithRecordsAndConfigurationAnnotation", configPropertiesComponentBean.getType());
|
||||
|
||||
List<SpringIndexElement> children = configPropertiesComponentBean.getChildren();
|
||||
assertEquals(2, children.size());
|
||||
|
||||
ConfigPropertyIndexElement configPropElement1 = (ConfigPropertyIndexElement) children.get(0);
|
||||
assertEquals("name", configPropElement1.getName());
|
||||
assertEquals("java.lang.String", configPropElement1.getType());
|
||||
|
||||
ConfigPropertyIndexElement configPropElement2 = (ConfigPropertyIndexElement) children.get(1);
|
||||
assertEquals("duration", configPropElement2.getName());
|
||||
assertEquals("int", configPropElement2.getType());
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "com.example.config.prefix.simple")
|
||||
@ConfigurationProperties(prefix = "com.example.config.prefix.simple2")
|
||||
public class ConfigurationPropertiesExampleWithConfigurationAnnotation {
|
||||
|
||||
private String simpleConfigProp = "default config value";
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.example.configproperties;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "com.example.config.record.prefix2")
|
||||
public record ConfigurationPropertiesWithRecordsAndConfigurationAnnotation (String name, int duration) {}
|
||||
Reference in New Issue
Block a user