Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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("(?<!\\.)\\*", ".*");
ParameterParseResult parsed = parseParameterList(query);
this.queryParameterTypes = parsed.types;
this.query = buildFqnRegexPattern(parsed, query);
this.symbolMatches = false;
this.match = match;
// depending on which location the query was for we only want to
Expand Down Expand Up @@ -299,53 +293,74 @@ public boolean visit(ClassInstanceCreation node) {
}

/**
* 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)"
* 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<String> extractParameterTypes(String query) {
// Performance: Quick check before indexOf
static final class ParameterParseResult {
final List<String> types;
final int paramOpenIndex;
final int paramCloseIndex;

private ParameterParseResult(List<String> 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<String> 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("(?<!\\.)\\*", ".*");
}

/**
* Parses the query for a trailing Java formal parameter list (first {@code '('} through last {@code ')'})
* when heuristics indicate alternation groups like {@code pkg.(A|B)} are not in play.
*/
static ParameterParseResult parseParameterList(String query) {
int openParen = query.indexOf('(');
if (openParen == -1) {
return null; // No parameters specified in query
return ParameterParseResult.none();
}

int closeParen = query.lastIndexOf(')');
if (closeParen == -1 || openParen >= 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<String> params = new ArrayList<>(4);

// Split by comma, handling nested generics like Map<String, Integer>
int depth = 0;
int start = 0;
int length = paramsString.length();
Expand All @@ -361,10 +376,21 @@ List<String> 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<String> extractParameterTypes(String query) {
return parseParameterList(query).types;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -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);
}
}
Loading