From 7877f14e477d0c8f2df5b9bd8f289e2579a4b0c7 Mon Sep 17 00:00:00 2001 From: Juan Manuel Leflet Estrada Date: Tue, 30 Jul 2024 13:18:41 +0200 Subject: [PATCH 1/3] Allow annotation queries to happen in ANNOTATION location Signed-off-by: Juan Manuel Leflet Estrada --- .../tackle/core/internal/RuleEntryParams.java | 2 +- .../core/internal/query/AnnotationQuery.java | 29 +++++++++++++++-- .../symbol/AnnotationSymbolProvider.java | 31 +++++++++++++++++-- .../internal/symbol/WithAnnotationQuery.java | 4 +-- 4 files changed, 57 insertions(+), 9 deletions(-) diff --git a/java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/RuleEntryParams.java b/java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/RuleEntryParams.java index 790cccd..790cdd6 100644 --- a/java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/RuleEntryParams.java +++ b/java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/RuleEntryParams.java @@ -25,8 +25,8 @@ public RuleEntryParams(final String commandId, final List arguments) { this.projectName = (String) obj.get("project"); this.query = (String) obj.get("query"); - this.annotationQuery = AnnotationQuery.fromMap((Map) obj.get("annotationQuery")); this.location = Integer.parseInt((String) obj.get("location")); + this.annotationQuery = AnnotationQuery.fromMap((Map) obj.get("annotationQuery"), location); this.analysisMode = (String) obj.get("analysisMode"); this.includedPaths = (ArrayList) obj.get("includedPaths"); } diff --git a/java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/query/AnnotationQuery.java b/java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/query/AnnotationQuery.java index c9e9b62..c6318ee 100644 --- a/java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/query/AnnotationQuery.java +++ b/java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/query/AnnotationQuery.java @@ -3,6 +3,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; /** * Represents additional query information to inspect annotations in annotated symbols. @@ -14,14 +15,20 @@ public class AnnotationQuery { */ private String type; + /** + * Indicates whether this AnnotationQuery is done on an annotation (location == ANNOTATION) + */ + private boolean isOnAnnotation; + /** * The elements within the annotation, ie, "value" in @BeanAnnotation(value = "value") */ private Map elements; - public AnnotationQuery(String type, Map elements) { + public AnnotationQuery(String type, Map elements, boolean isOnAnnotation) { this.type = type; this.elements = elements; + this.isOnAnnotation = isOnAnnotation; } public String getType() { @@ -32,7 +39,23 @@ public Map getElements() { return elements; } - public static AnnotationQuery fromMap(Map query) { + public boolean isOnAnnotation() { + return this.isOnAnnotation; + } + + /** + * Checks whether the query matches against a given annotation + */ + public boolean matchesAnnotation(String annotation) { + // If the annotation query is happening on an annotation, the annotation field in the annotation query can be null + if (isOnAnnotation() && getType() == null) { + return true; + } else { + return Pattern.matches(getType(), annotation); + } + } + + public static AnnotationQuery fromMap(Map query, int location) { if (query == null) { return null; } @@ -46,6 +69,6 @@ public static AnnotationQuery fromMap(Map query) { elements.put(key, value); } - return new AnnotationQuery(typePattern, elements); + return new AnnotationQuery(typePattern, elements, location == 4); } } diff --git a/java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/AnnotationSymbolProvider.java b/java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/AnnotationSymbolProvider.java index 6e3eef8..4101aa6 100644 --- a/java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/AnnotationSymbolProvider.java +++ b/java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/AnnotationSymbolProvider.java @@ -5,14 +5,22 @@ import java.util.ArrayList; import java.util.List; +import io.konveyor.tackle.core.internal.query.AnnotationQuery; import org.eclipse.core.runtime.CoreException; import org.eclipse.jdt.core.IAnnotatable; import org.eclipse.jdt.core.IAnnotation; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.search.SearchMatch; +import org.eclipse.jdt.internal.core.ResolvedSourceField; +import org.eclipse.jdt.internal.core.ResolvedSourceMethod; +import org.eclipse.jdt.internal.core.ResolvedSourceType; +import org.eclipse.jdt.internal.core.SourceRefElement; import org.eclipse.lsp4j.SymbolInformation; -public class AnnotationSymbolProvider implements SymbolProvider { +public class AnnotationSymbolProvider implements SymbolProvider, WithAnnotationQuery { + + private AnnotationQuery annotationQuery; + @Override public List get(SearchMatch match) throws CoreException { List symbols = new ArrayList<>(); @@ -25,7 +33,18 @@ public List get(SearchMatch match) throws CoreException { symbol.setKind(convertSymbolKind(element)); symbol.setContainerName(annotation.getParent().getElementName()); symbol.setLocation(getLocation(element, match)); - symbols.add(symbol); + + if (annotationQuery != null) { + List> classes = new ArrayList<>(); + classes.add(ResolvedSourceMethod.class); + classes.add(ResolvedSourceField.class); + classes.add(ResolvedSourceType.class); + if (matchesAnnotationQuery(match, classes)) { + symbols.add(symbol); + } + } else { + symbols.add(symbol); + } } return symbols; } catch (Exception e) { @@ -33,4 +52,12 @@ public List get(SearchMatch match) throws CoreException { return null; } } + + public AnnotationQuery getAnnotationQuery() { + return annotationQuery; + } + + public void setAnnotationQuery(AnnotationQuery annotationQuery) { + this.annotationQuery = annotationQuery; + } } diff --git a/java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/WithAnnotationQuery.java b/java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/WithAnnotationQuery.java index c557b1f..8664eed 100644 --- a/java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/WithAnnotationQuery.java +++ b/java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/WithAnnotationQuery.java @@ -2,13 +2,11 @@ import io.konveyor.tackle.core.internal.query.AnnotationQuery; import org.eclipse.jdt.core.IAnnotation; -import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IImportDeclaration; import org.eclipse.jdt.core.IMemberValuePair; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.search.SearchMatch; import org.eclipse.jdt.internal.core.Annotation; -import org.eclipse.jdt.internal.core.CompilationUnit; import org.eclipse.jdt.internal.core.SourceRefElement; import java.util.Arrays; @@ -51,7 +49,7 @@ default boolean matchesAnnotationQuery(SearchMatch match, List Date: Tue, 13 Aug 2024 15:50:18 +0200 Subject: [PATCH 2/3] Annotation query can have empty pattern Signed-off-by: Juan Manuel Leflet Estrada --- .../tackle/core/internal/RuleEntryParams.java | 2 +- .../tackle/core/internal/query/AnnotationQuery.java | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/RuleEntryParams.java b/java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/RuleEntryParams.java index 790cdd6..9dbbf7a 100644 --- a/java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/RuleEntryParams.java +++ b/java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/RuleEntryParams.java @@ -26,7 +26,7 @@ public RuleEntryParams(final String commandId, final List arguments) { this.projectName = (String) obj.get("project"); this.query = (String) obj.get("query"); this.location = Integer.parseInt((String) obj.get("location")); - this.annotationQuery = AnnotationQuery.fromMap((Map) obj.get("annotationQuery"), location); + this.annotationQuery = AnnotationQuery.fromMap(this.query, (Map) obj.get("annotationQuery"), location); this.analysisMode = (String) obj.get("analysisMode"); this.includedPaths = (ArrayList) obj.get("includedPaths"); } diff --git a/java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/query/AnnotationQuery.java b/java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/query/AnnotationQuery.java index c6318ee..356a95b 100644 --- a/java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/query/AnnotationQuery.java +++ b/java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/query/AnnotationQuery.java @@ -55,20 +55,21 @@ public boolean matchesAnnotation(String annotation) { } } - public static AnnotationQuery fromMap(Map query, int location) { - if (query == null) { + public static AnnotationQuery fromMap(String query, Map annotationQuery, int location) { + if (annotationQuery == null) { return null; } - String typePattern = (String) query.get("pattern"); + boolean isOnAnnotation = location == 4; + String typePattern = isOnAnnotation && annotationQuery.get("pattern").equals("") ? query : (String) annotationQuery.get("pattern");; final Map elements = new HashMap<>(); - List> mapElements = (List>) query.get("elements"); + List> mapElements = (List>) annotationQuery.get("elements"); for (int i = 0; mapElements != null && i < mapElements.size(); i++) { String key = mapElements.get(i).get("name"); String value = mapElements.get(i).get("value"); elements.put(key, value); } - return new AnnotationQuery(typePattern, elements, location == 4); + return new AnnotationQuery(typePattern, elements, isOnAnnotation); } } From dd160b32825531279f4e218f39310e3e5ed5af45 Mon Sep 17 00:00:00 2001 From: Juan Manuel Leflet Estrada Date: Thu, 22 Aug 2024 13:31:45 +0200 Subject: [PATCH 3/3] Allow for inspection of annotations within annotations Refactor and cleanup Signed-off-by: Juan Manuel Leflet Estrada --- .../internal/symbol/WithAnnotationQuery.java | 124 ++++++++++++++---- 1 file changed, 102 insertions(+), 22 deletions(-) diff --git a/java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/WithAnnotationQuery.java b/java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/WithAnnotationQuery.java index 8664eed..5b54b05 100644 --- a/java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/WithAnnotationQuery.java +++ b/java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/WithAnnotationQuery.java @@ -50,33 +50,25 @@ default boolean matchesAnnotationQuery(SearchMatch match, List> annotationElements = getAnnotationQuery().getElements().entrySet(); - for (IMemberValuePair member : memberValuePairs) { - for (Map.Entry annotationElement : annotationElements) { - if (annotationElement.getKey().equals(member.getMemberName())) { - // Member values can be arrays. In this case, lets iterate over it and compare: - if (member.getValue() instanceof Object[]) { - Object[] values = (Object[]) member.getValue(); - // TODO: at the moment we are just toString()ing the values. - // We might want to make this more sophisticated, relying on - // member.getValueKind() to match on specific kinds. - return Arrays.stream(values).anyMatch(v -> Pattern.matches(annotationElement.getValue(), v.toString())); - } else { - if (Pattern.matches(annotationElement.getValue(), member.getValue().toString())) { - return true; - } + return doElementsMatch((Annotation) annotation); + } else { + // The LS doesn't seem to be able to match on annotations within annotations, but + // if the main annotation doesn't match, there might be some annotations inside: + for (IMemberValuePair member : annotation.getMemberValuePairs()) { + if (member.getValueKind() == IMemberValuePair.K_ANNOTATION) { + if (member.getValue() instanceof Object[]) { + Object[] objs = (Object[]) member.getValue(); + for (int i = 0; i < objs.length; i++) { + Annotation innerAnnotation = (Annotation) objs[i]; + fqn = getFQN(innerAnnotation); + if (getAnnotationQuery().matchesAnnotation(fqn)) { + return doElementsMatch(innerAnnotation); } } } - } - } else { - // No annotation elements, but the annotation itself matches - return true; } + } } return false; @@ -89,6 +81,94 @@ default boolean matchesAnnotationQuery(SearchMatch match, List + * {@code + * @DataSourceDefinitions({ + * @DataSourceDefinition( + * name = "jdbc/multiple-ds-xa", + * className="com.example.MyDataSource", + * portNumber=6689, + * serverName="example.com", + * user="lance", + * password="secret" + * ), + * @DataSourceDefinition( + * name = "jdbc/multiple-ds-non-xa", + * className="com.example.MyDataSource", + * portNumber=6689, + * serverName="example.com", + * user="lance", + * password="secret", + * transactional = false + * ), + * }) + * public class AnnotationMultipleDs { + * } + * + * + * + * If we have a query like this: + *
+     *   when:
+     *     java.referenced:
+     *       location: ANNOTATION
+     *       pattern: javax.annotation.sql.DataSourceDefinition
+     *       annotated:
+     *         elements:
+     *           - name: transactional
+     *             value: false
+     * 
+ * we need to check if the annotation elements (the different config properties defined within the annotation) + * match the one(s) in the query. The query can define more than one element, as shown by the fact that the + * "elements" node is an array, therefore, if multiple elements are present in the query, all must be matched. + * + * @param annotation the annotation to inspect + * @return a boolean indicating whether the elements match + * @throws JavaModelException + */ + private boolean doElementsMatch(Annotation annotation) throws JavaModelException { + // If the query has annotation elements to check, iterate through the annotation's values and check + if (getAnnotationQuery().getElements() != null && !getAnnotationQuery().getElements().entrySet().isEmpty()) { + IMemberValuePair[] memberValuePairs = annotation.getMemberValuePairs(); + Set> ruleAnnotationElems = getAnnotationQuery().getElements().entrySet(); + boolean allElementsMatch = true; + boolean oneElementMatched = false; + // TODO: there is a problem with defaults: they don't appear in the memberValuePairs so they cannot be matched + for (int i = 0; i < memberValuePairs.length && allElementsMatch; i++) { + IMemberValuePair member = memberValuePairs[i]; + for (Map.Entry ruleAnnotationElem : ruleAnnotationElems) { + String ruleAnnotationElementName = ruleAnnotationElem.getKey(); + if (ruleAnnotationElementName.equals(member.getMemberName())) { + // Member values can be arrays. In this case, lets iterate over it and compare: + if (member.getValue() instanceof Object[]) { + Object[] values = (Object[]) member.getValue(); + // TODO: at the moment we are just toString()ing the values. + // We might want to make this more sophisticated, relying on + // member.getValueKind() to match on specific kinds. This however can match + boolean valueMatches = Arrays.stream(values).anyMatch(v -> Pattern.matches(ruleAnnotationElem.getValue(), v.toString())); + oneElementMatched |= valueMatches; + allElementsMatch &= valueMatches; + } else { + boolean valueMatches = Pattern.matches(ruleAnnotationElem.getValue(), member.getValue().toString()); + oneElementMatched |= valueMatches; + allElementsMatch &= valueMatches; + } + } + } + } + return oneElementMatched && allElementsMatch; + } + + // No annotation elements, but the annotation itself matches + return true; + + } + private IAnnotation[] tryToGetAnnotations(SourceRefElement t) { try { return t.getAnnotations();