diff --git a/java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/CustomASTVisitor.java b/java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/CustomASTVisitor.java index 94f3a2b..d6925a6 100644 --- a/java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/CustomASTVisitor.java +++ b/java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/CustomASTVisitor.java @@ -7,7 +7,6 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import java.lang.reflect.Parameter; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTVisitor; @@ -52,17 +51,12 @@ public CustomASTVisitor(String query, SearchMatch match, QueryLocation location) /* * Extract parameter types from the query pattern if present * e.g., "java.util.Properties.setProperty(java.lang.String, java.lang.String)" - * We'll extract ["java.lang.String", "java.lang.String"] and strip the parameters from the query + * We'll extract ["java.lang.String", "java.lang.String"] and strip only that parameter list + * from the query for FQN matching — not parenthetical alternation like "pkg.(A|B)". */ - this.queryParameterTypes = extractParameterTypes(query); - String processedQuery = query.replaceAll("\\([^)]*\\)", ""); - - /* - * When comparing query pattern with an actual java element's fqn - * we need to make sure that * not preceded with a . are replaced - * by .* so that java regex works as expected on them - */ - this.query = processedQuery.replaceAll("(? ["Type1", "Type2"] - * Returns null if no parameters are specified in the query - * - * Note: Must distinguish between method parameters like "method(String, int)" - * and regex alternation groups like "java.io.(FileWriter|FileReader)" + * Result of parsing a query for an optional trailing Java parameter list. + * When {@code types != null}, {@code paramOpenIndex} and {@code paramCloseIndex} bound the + * {@code (...)} segment to remove for FQN matching (indices of {@code '('} and {@code ')'}). */ - List extractParameterTypes(String query) { - // Performance: Quick check before indexOf + static final class ParameterParseResult { + final List types; + final int paramOpenIndex; + final int paramCloseIndex; + + private ParameterParseResult(List types, int paramOpenIndex, int paramCloseIndex) { + this.types = types; + this.paramOpenIndex = paramOpenIndex; + this.paramCloseIndex = paramCloseIndex; + } + + static ParameterParseResult none() { + return new ParameterParseResult(null, -1, -1); + } + + static ParameterParseResult withTypes(List types, int paramOpenIndex, int paramCloseIndex) { + return new ParameterParseResult(types, paramOpenIndex, paramCloseIndex); + } + } + + /** + * Strips only a successfully parsed parameter list, then normalizes unqualified {@code *} to {@code .*} for regex. + * Package-private for unit tests (same package). + */ + static String buildFqnRegexPattern(ParameterParseResult parsed, String query) { + String processed = query; + if (parsed.types != null) { + processed = query.substring(0, parsed.paramOpenIndex) + query.substring(parsed.paramCloseIndex + 1); + } + return processed.replaceAll("(?= closeParen) { - return null; // Invalid or no parameters + return ParameterParseResult.none(); } - // Check if this looks like method parameters vs regex alternation - // Method parameters: "ClassName.methodName(Type1, Type2)" - parens at END with optional * after - // Regex alternation: "java.io.(FileWriter|FileReader)*" - parens in MIDDLE with content after - // - // Heuristic: If there's a '|' (pipe) character inside the parentheses, it's likely - // a regex alternation group, not method parameters String potentialParams = query.substring(openParen + 1, closeParen); if (potentialParams.contains("|")) { - return null; // Regex alternation, not method parameters + return ParameterParseResult.none(); } - // Another check: method parameters should be near the end of the pattern - // Look for content after the closing paren (besides wildcards * which are common) String afterParen = query.substring(closeParen + 1).trim(); if (afterParen.length() > 0 && !afterParen.matches("\\**")) { - // There's significant content after the closing paren, not method parameters - return null; + return ParameterParseResult.none(); } String paramsString = potentialParams.trim(); if (paramsString.isEmpty()) { - return Collections.emptyList(); // Empty parameter list: method() + return ParameterParseResult.withTypes(Collections.emptyList(), openParen, closeParen); } - // Performance: Pre-size ArrayList for common cases (most methods have 1-3 params) List params = new ArrayList<>(4); - // Split by comma, handling nested generics like Map int depth = 0; int start = 0; int length = paramsString.length(); @@ -361,10 +376,21 @@ List extractParameterTypes(String query) { start = i + 1; } } - // Add the last parameter params.add(paramsString.substring(start).trim()); - return params; + return ParameterParseResult.withTypes(params, openParen, closeParen); + } + + /** + * Extracts parameter types from a query pattern. + * e.g., "ClassName.method(Type1, Type2)" -> ["Type1", "Type2"] + * Returns null if no parameters are specified in the query + * + * Note: Must distinguish between method parameters like "method(String, int)" + * and regex alternation groups like "java.io.(FileWriter|FileReader)" + */ + List extractParameterTypes(String query) { + return parseParameterList(query).types; } /** diff --git a/java-analyzer-bundle.test/src/main/java/io/konveyor/tackle/core/internal/symbol/CustomASTVisitorTest.java b/java-analyzer-bundle.test/src/main/java/io/konveyor/tackle/core/internal/symbol/CustomASTVisitorTest.java index 8bea4c6..8fdbc2b 100644 --- a/java-analyzer-bundle.test/src/main/java/io/konveyor/tackle/core/internal/symbol/CustomASTVisitorTest.java +++ b/java-analyzer-bundle.test/src/main/java/io/konveyor/tackle/core/internal/symbol/CustomASTVisitorTest.java @@ -1,6 +1,8 @@ package io.konveyor.tackle.core.internal.symbol; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -151,4 +153,60 @@ public void testExtractParameterTypes_varargs() { assertEquals("Should handle varargs notation", Arrays.asList("java.lang.String..."), result); } + + @Test + public void testParseParameterList_alternationReturnsNone() { + assertNull(CustomASTVisitor.parseParameterList("org.library.(Class1|Class2)").types); + assertNull(CustomASTVisitor.parseParameterList("java.io.(FileWriter|FileReader)").types); + assertNull(CustomASTVisitor.parseParameterList("java.io.(FileWriter|FileReader|PrintStream)").types); + } + + @Test + public void testBuildFqnRegexPattern_preservesAlternationForMatching() { + String query = "org.library.(Class1|Class2)"; + String regex = CustomASTVisitor.buildFqnRegexPattern( + CustomASTVisitor.parseParameterList(query), query); + assertEquals("org.library.(Class1|Class2)", regex); + assertTrue("org.library.Class1".matches(regex)); + assertTrue("org.library.Class2".matches(regex)); + assertFalse("org.library.Class3".matches(regex)); + } + + @Test + public void testBuildFqnRegexPattern_stripsTrailingParameterListOnly() { + String query = "java.util.Properties.setProperty(java.lang.String, java.lang.String)"; + CustomASTVisitor.ParameterParseResult p = CustomASTVisitor.parseParameterList(query); + assertNotNull(p.types); + String regex = CustomASTVisitor.buildFqnRegexPattern(p, query); + assertEquals("java.util.Properties.setProperty", regex); + assertTrue("java.util.Properties.setProperty".matches(regex)); + } + + @Test + public void testBuildFqnRegexPattern_emptyParameterListStripped() { + String query = "com.example.ClassName.method()"; + CustomASTVisitor.ParameterParseResult p = CustomASTVisitor.parseParameterList(query); + assertNotNull(p.types); + assertTrue(p.types.isEmpty()); + String regex = CustomASTVisitor.buildFqnRegexPattern(p, query); + assertEquals("com.example.ClassName.method", regex); + } + + @Test + public void testBuildFqnRegexPattern_alternationWithWildcardSuffix() { + String query = "java.io.(FileWriter|FileReader)*"; + CustomASTVisitor.ParameterParseResult p = CustomASTVisitor.parseParameterList(query); + assertNull("Alternation must not be parsed as parameter list", p.types); + String regex = CustomASTVisitor.buildFqnRegexPattern(p, query); + assertEquals("java.io.(FileWriter|FileReader).*", regex); + } + + @Test + public void testBuildFqnRegexPattern_methodParamsThenWildcard() { + String query = "com.example.Foo.bar(java.lang.String)*"; + CustomASTVisitor.ParameterParseResult p = CustomASTVisitor.parseParameterList(query); + assertNotNull(p.types); + String regex = CustomASTVisitor.buildFqnRegexPattern(p, query); + assertEquals("com.example.Foo.bar.*", regex); + } }