Skip to content

Commit

Permalink
GH-1305: find all references for property keys now supported on Condi…
Browse files Browse the repository at this point in the history
…tionalOnProperty annotation
  • Loading branch information
martinlippert committed Nov 6, 2024
1 parent 790629e commit 4258612
Show file tree
Hide file tree
Showing 6 changed files with 284 additions and 121 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@
import org.springframework.ide.vscode.boot.java.handlers.BootJavaReferencesHandler;
import org.springframework.ide.vscode.boot.java.handlers.BootJavaWorkspaceSymbolHandler;
import org.springframework.ide.vscode.boot.java.handlers.CodeLensProvider;
import org.springframework.ide.vscode.boot.java.handlers.CopilotCodeLensProvider;
import org.springframework.ide.vscode.boot.java.handlers.HighlightProvider;
import org.springframework.ide.vscode.boot.java.handlers.HoverProvider;
import org.springframework.ide.vscode.boot.java.handlers.CopilotCodeLensProvider;
import org.springframework.ide.vscode.boot.java.handlers.ReferenceProvider;
import org.springframework.ide.vscode.boot.java.links.SourceLinks;
import org.springframework.ide.vscode.boot.java.livehover.ActiveProfilesProvider;
Expand Down Expand Up @@ -319,6 +319,7 @@ protected ReferencesHandler createReferenceHandler(SimpleLanguageServer server,
Map<String, ReferenceProvider> providers = new HashMap<>();

providers.put(Annotations.VALUE, new ValuePropertyReferencesProvider(projectFinder));
providers.put(Annotations.CONDITIONAL_ON_PROPERTY, new ValuePropertyReferencesProvider(projectFinder));
providers.put(Annotations.QUALIFIER, new QualifierReferencesProvider(index, symbolIndex));
providers.put(Annotations.NAMED_JAKARTA, new NamedReferencesProvider(index, symbolIndex));
providers.put(Annotations.NAMED_JAVAX, new NamedReferencesProvider(index, symbolIndex));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*******************************************************************************
* 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.value;

import java.util.List;
import java.util.Map;

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.Expression;
import org.eclipse.jdt.core.dom.IAnnotationBinding;
import org.eclipse.jdt.core.dom.MemberValuePair;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.lsp4j.LocationLink;
import org.springframework.ide.vscode.boot.java.Annotations;
import org.springframework.ide.vscode.commons.java.IJavaProject;

public class PropertyExtractor {

private static final String PARAM_VALUE = "value";
private static final String PARAM_NAME = "name";
private static final String PARAM_PREFIX = "prefix";

private static interface PropertyKeyExtractor {
String extract(Annotation annotation, MemberValuePair memberValuePair, StringLiteral stringLiteral);
}

private final Map<String, PropertyKeyExtractor> propertyKeyExtractors;

public PropertyExtractor() {
propertyKeyExtractors = Map.of(

Annotations.VALUE, (annotation, memberValuePair, stringLiteral) -> {
if (annotation.isSingleMemberAnnotation()) {
return extractPropertyKey(stringLiteral.getLiteralValue());
} else if (annotation.isNormalAnnotation() && PARAM_VALUE.equals(memberValuePair.getName().getIdentifier())) {
return extractPropertyKey(stringLiteral.getLiteralValue());
}
return null;
},

Annotations.CONDITIONAL_ON_PROPERTY, (annotation, memberValuePair, stringLiteral) -> {
if (annotation.isSingleMemberAnnotation()) {
return stringLiteral.getLiteralValue();
} else if (annotation.isNormalAnnotation()) {
switch (memberValuePair.getName().getIdentifier()) {
case PARAM_VALUE:
return stringLiteral.getLiteralValue();
case PARAM_NAME:
String prefix = extractAnnotationParameter(annotation, PARAM_PREFIX);
String name = stringLiteral.getLiteralValue();
return prefix != null && !prefix.isBlank() ? prefix + "." + name : name;
}
}
return null;
}
);
}

public String extractPropertyKey(StringLiteral valueNode) {

ASTNode parent = valueNode.getParent();

if (parent instanceof Annotation) {

Annotation a = (Annotation) parent;
IAnnotationBinding binding = a.resolveAnnotationBinding();
if (binding != null && binding.getAnnotationType() != null) {
PropertyKeyExtractor propertyExtractor = propertyKeyExtractors.get(binding.getAnnotationType().getQualifiedName());
if (propertyExtractor != null) {
return propertyExtractor.extract(a, null, valueNode);
}
}

} else if (parent instanceof MemberValuePair && parent.getParent() instanceof Annotation) {

MemberValuePair pair = (MemberValuePair) parent;
Annotation a = (Annotation) parent.getParent();
IAnnotationBinding binding = a.resolveAnnotationBinding();
if (binding != null && binding.getAnnotationType() != null) {
PropertyKeyExtractor propertyExtractor = propertyKeyExtractors.get(binding.getAnnotationType().getQualifiedName());
if (propertyExtractor != null) {
return propertyExtractor.extract(a, pair, valueNode);
}
}
}

return null;
}

private String extractPropertyKey(String s) {
if (s.length() > 3 && (s.startsWith("${") || s.startsWith("#{")) && s.endsWith("}")) {
return s.substring(2, s.length() - 1);
}
return null;
}

@SuppressWarnings("unchecked")
private static String extractAnnotationParameter(Annotation a, String param) {
Expression value = null;
if (a.isSingleMemberAnnotation() && PARAM_VALUE.equals(param)) {
value = ((SingleMemberAnnotation) a).getValue();
} else if (a.isNormalAnnotation()) {
for (MemberValuePair pair : (List<MemberValuePair>) ((NormalAnnotation) a).values()) {
if (param.equals(pair.getName().getIdentifier())) {
value = pair.getValue();
break;
}
}
}
if (value instanceof StringLiteral) {
return ((StringLiteral) value).getLiteralValue();
}
return null;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,7 @@
import java.util.Optional;

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.Expression;
import org.eclipse.jdt.core.dom.IAnnotationBinding;
import org.eclipse.jdt.core.dom.MemberValuePair;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.LocationLink;
Expand All @@ -34,7 +28,6 @@
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.Annotations;
import org.springframework.ide.vscode.boot.java.IJavaDefinitionProvider;
import org.springframework.ide.vscode.boot.properties.BootPropertiesLanguageServerComponents;
import org.springframework.ide.vscode.commons.java.IClasspathUtil;
Expand All @@ -47,37 +40,12 @@
public class ValueDefinitionProvider implements IJavaDefinitionProvider {

private static final Logger log = LoggerFactory.getLogger(ValueDefinitionProvider.class);
private final PropertyExtractor propertyExtractor;

private static final String PARAM_VALUE = "value";
private static final String PARAM_NAME = "name";
private static final String PARAM_PREFIX = "prefix";
public ValueDefinitionProvider() {
this.propertyExtractor = new PropertyExtractor();
}

private Map<String, PropertyKeyExtractor> annotationToPropertyKeyExtractor = Map.of(
Annotations.VALUE, (annotation, memberValuePair, stringLiteral) -> {
if (annotation.isSingleMemberAnnotation()) {
return extractPropertyKey(stringLiteral.getLiteralValue());
} else if (annotation.isNormalAnnotation() && PARAM_VALUE.equals(memberValuePair.getName().getIdentifier())) {
return extractPropertyKey(stringLiteral.getLiteralValue());
}
return null;
},
Annotations.CONDITIONAL_ON_PROPERTY, (annotation, memberValuePair, stringLiteral) -> {
if (annotation.isSingleMemberAnnotation()) {
return stringLiteral.getLiteralValue();
} else if (annotation.isNormalAnnotation()) {
switch (memberValuePair.getName().getIdentifier()) {
case PARAM_VALUE:
return stringLiteral.getLiteralValue();
case PARAM_NAME:
String prefix = extractAnnotationParameter(annotation, PARAM_PREFIX);
String name = stringLiteral.getLiteralValue();
return prefix != null && !prefix.isBlank() ? prefix + "." + name : name;
}
}
return null;
}
);

@Override
public List<LocationLink> getDefinitions(CancelChecker cancelToken, IJavaProject project,
TextDocumentIdentifier docId, CompilationUnit cu, ASTNode n, int offset) {
Expand All @@ -99,30 +67,7 @@ public List<LocationLink> getDefinitions(CancelChecker cancelToken, IJavaProject
}

private List<LocationLink> getDefinitionForProperty(IJavaProject project, CompilationUnit cu, StringLiteral valueNode) {
String propertyKey = null;

ASTNode parent = valueNode.getParent();
if (parent instanceof Annotation) {
Annotation a = (Annotation) parent;
IAnnotationBinding binding = a.resolveAnnotationBinding();
if (binding != null && binding.getAnnotationType() != null) {
PropertyKeyExtractor propertyExtractor = annotationToPropertyKeyExtractor.get(binding.getAnnotationType().getQualifiedName());
if (propertyExtractor != null) {
propertyKey = propertyExtractor.extract(a, null, valueNode);
}
}
} else if (parent instanceof MemberValuePair
&& parent.getParent() instanceof Annotation) {
MemberValuePair pair = (MemberValuePair) parent;
Annotation a = (Annotation) parent.getParent();
IAnnotationBinding binding = a.resolveAnnotationBinding();
if (binding != null && binding.getAnnotationType() != null) {
PropertyKeyExtractor propertyExtractor = annotationToPropertyKeyExtractor.get(binding.getAnnotationType().getQualifiedName());
if (propertyExtractor != null) {
propertyKey = propertyExtractor.extract(a, pair, valueNode);
}
}
}
String propertyKey = propertyExtractor.extractPropertyKey(valueNode);

if (propertyKey != null) {
Builder<LocationLink> builder = ImmutableList.builder();
Expand Down Expand Up @@ -217,36 +162,6 @@ private List<Location> findValueReferences(IJavaProject project, String property
return links.build();
}

@SuppressWarnings("unchecked")
private static String extractAnnotationParameter(Annotation a, String param) {
Expression value = null;
if (a.isSingleMemberAnnotation() && PARAM_VALUE.equals(param)) {
value = ((SingleMemberAnnotation) a).getValue();
} else if (a.isNormalAnnotation()) {
for (MemberValuePair pair : (List<MemberValuePair>) ((NormalAnnotation) a).values()) {
if (param.equals(pair.getName().getIdentifier())) {
value = pair.getValue();
break;
}
}
}
if (value instanceof StringLiteral) {
return ((StringLiteral) value).getLiteralValue();
}
return null;
}

private static String extractPropertyKey(String s) {
if (s.length() > 3 && (s.startsWith("${") || s.startsWith("#{")) && s.endsWith("}")) {
return s.substring(2, s.length() - 1);
}
return null;
}

private interface PropertyKeyExtractor {
String extract(Annotation annotation, MemberValuePair memberValuePair, StringLiteral stringLiteral);
}

private List<LocationLink> getDefinitionForClasspathResource(IJavaProject project, CompilationUnit cu, StringLiteral valueNode, String literalValue) {
literalValue = literalValue.substring("classpath:".length());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MemberValuePair;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.Position;
Expand Down Expand Up @@ -65,9 +64,11 @@ public class ValuePropertyReferencesProvider implements ReferenceProvider {
private static final Logger log = LoggerFactory.getLogger(ValuePropertyReferencesProvider.class);

private final JavaProjectFinder projectFinder;
private final PropertyExtractor propertyExtractor;

public ValuePropertyReferencesProvider(JavaProjectFinder projectFinder) {
this.projectFinder = projectFinder;
this.propertyExtractor = new PropertyExtractor();
}

@Override
Expand All @@ -76,34 +77,9 @@ public List<? extends Location> provideReferences(CancelChecker cancelToken, IJa
cancelToken.checkCanceled();

try {
// case: @Value("prefix<*>")
if (node instanceof StringLiteral && node.getParent() instanceof Annotation) {
if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) {
return provideReferences(node.toString(), offset - node.getStartPosition(), node.getStartPosition());
}
}
// case: @Value(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("\"")) {
return provideReferences(node.toString(), offset - node.getStartPosition(), node.getStartPosition());
}
}
}
catch (Exception e) {
e.printStackTrace();
}

return null;
}

private List<? extends Location> provideReferences(String value, int offset, int nodeStartOffset) {

try {
LocalRange range = getPropertyRange(value, offset);
if (range != null) {
String propertyKey = value.substring(range.getStart(), range.getEnd());
if (propertyKey != null && propertyKey.length() > 0) {
if (node instanceof StringLiteral) {
String propertyKey = this.propertyExtractor.extractPropertyKey((StringLiteral) node);
if (propertyKey != null) {
return findReferencesFromPropertyFiles(propertyKey);
}
}
Expand Down
Loading

0 comments on commit 4258612

Please sign in to comment.