Add context locator for Kotlin function definitions

Implemented Kotlin-specific context locator to handle function
definition requests in Java projects.

Change-Id: I86a3baae574870b197dc5f2e674e8e3fde8ae22b
Signed-off-by: Patrizio <patrizio.gelosi@amarulasolutions.com>
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/code/context/ondemand/locator/CallableLocatorBase.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/code/context/ondemand/locator/CallableLocatorBase.java
index 637b3e8..7fde3d5 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/code/context/ondemand/locator/CallableLocatorBase.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/code/context/ondemand/locator/CallableLocatorBase.java
@@ -28,6 +28,7 @@
     protected final List<String> importModules = new ArrayList<>();
     protected final CodeFileFetcher codeFileFetcher;
 
+    protected Pattern importPattern;
     protected String languageModuleExtension;
     protected String rootFileDir;
 
@@ -52,7 +53,9 @@
 
     protected abstract String getFunctionRegex(String functionName);
 
-    protected abstract String findImportedFunctionDefinition(String functionName, String content);
+    protected abstract void parseImportStatements(String content);
+
+    protected abstract String findInImportModules(String functionName);
 
     protected String getFunctionFromModule(String functionName, String module) {
         String modulePath = convertDotNotationToPath(module) + languageModuleExtension;
@@ -77,9 +80,16 @@
         return null;
     }
 
+    private String findImportedFunctionDefinition(String functionName, String content) {
+        parseImportStatements(content);
+
+        return findInImportModules(functionName);
+    }
+
     private String findFunctionInFile(String filename, String functionName) {
         log.debug("Finding function {} in file {}", functionName, filename);
         if (visitedFiles.contains(filename)) {
+            log.debug("File {} already visited", filename);
             return null;
         }
         visitedFiles.add(filename);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/code/context/ondemand/locator/CallableLocatorJVM.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/code/context/ondemand/locator/CallableLocatorJVM.java
new file mode 100644
index 0000000..78cdac5
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/code/context/ondemand/locator/CallableLocatorJVM.java
@@ -0,0 +1,51 @@
+package com.googlesource.gerrit.plugins.chatgpt.mode.stateful.client.code.context.ondemand.locator;
+
+import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
+import com.googlesource.gerrit.plugins.chatgpt.interfaces.mode.stateful.client.code.context.ondemand.IEntityLocator;
+import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.api.gerrit.GerritChange;
+import com.googlesource.gerrit.plugins.chatgpt.mode.stateful.client.api.git.GitRepoFiles;
+import com.googlesource.gerrit.plugins.chatgpt.utils.FileUtils;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.List;
+import java.util.regex.Matcher;
+
+@Slf4j
+public abstract class CallableLocatorJVM extends CallableLocatorBase implements IEntityLocator {
+    public CallableLocatorJVM(Configuration config, GerritChange change, GitRepoFiles gitRepoFiles) {
+        super(config, change, gitRepoFiles);
+        log.debug("Initializing JVM CallableLocator");
+    }
+
+    @Override
+    protected void parseImportStatements(String content) {
+        parseDirectImportStatements(content, importModules);
+        retrievePackageModules();
+    }
+
+    @Override
+    protected String findInImportModules(String functionName) {
+        return findInModules(functionName);
+    }
+
+    protected void parseDirectImportStatements(String content, List<String> importModules) {
+        log.debug("Parsing import statements");
+        Matcher importMatcher = importPattern.matcher(content);
+        while (importMatcher.find()) {
+            String importModulesGroup = importMatcher.group(1);
+            log.debug("Parsing import module: `{}`", importModulesGroup);
+            importModules.add(importModulesGroup);
+        }
+        log.debug("Found import modules from import statements: {}", importModules);
+    }
+
+    protected void retrievePackageModules() {
+        log.debug("Retrieving modules from current package");
+        List<String> packageModules = codeFileFetcher.getFilesInDir(rootFileDir)
+                .stream()
+                .map(FileUtils::removeExtension)
+                .toList();
+        log.debug("Modules retrieved from current package: {}", packageModules);
+        importModules.addAll(packageModules);
+    }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/code/context/ondemand/locator/CodeLocatorFactory.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/code/context/ondemand/locator/CodeLocatorFactory.java
index 53b3778..a4e40d2 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/code/context/ondemand/locator/CodeLocatorFactory.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/code/context/ondemand/locator/CodeLocatorFactory.java
@@ -28,6 +28,7 @@
     private static final Map<String, String> MAP_EXTENSION = Map.of(
             "py", "python",
             "java", "java",
+            "kt", "kotlin",
             "c", "c",
             "h", "c"
     );
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/code/context/ondemand/locator/language/java/CallableLocator.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/code/context/ondemand/locator/language/java/CallableLocator.java
index b02aec6..b0f7715 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/code/context/ondemand/locator/language/java/CallableLocator.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/code/context/ondemand/locator/language/java/CallableLocator.java
@@ -4,26 +4,23 @@
 import com.googlesource.gerrit.plugins.chatgpt.interfaces.mode.stateful.client.code.context.ondemand.IEntityLocator;
 import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.api.gerrit.GerritChange;
 import com.googlesource.gerrit.plugins.chatgpt.mode.stateful.client.api.git.GitRepoFiles;
-import com.googlesource.gerrit.plugins.chatgpt.mode.stateful.client.code.context.ondemand.locator.CallableLocatorBase;
-import com.googlesource.gerrit.plugins.chatgpt.utils.FileUtils;
+import com.googlesource.gerrit.plugins.chatgpt.mode.stateful.client.code.context.ondemand.locator.CallableLocatorJVM;
 import lombok.extern.slf4j.Slf4j;
 
-import java.util.List;
-import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 @Slf4j
-public class CallableLocator extends CallableLocatorBase implements IEntityLocator {
+public class CallableLocator extends CallableLocatorJVM implements IEntityLocator {
     private static final String JAVA_MODULE_EXTENSION = ".java";
-    private static final Pattern IMPORT_PATTERN = Pattern.compile(
-            String.format("^import\\s+(?:static\\s+)?(%s)", DOT_NOTATION_REGEX),
-            Pattern.MULTILINE
-    );
 
     public CallableLocator(Configuration config, GerritChange change, GitRepoFiles gitRepoFiles) {
         super(config, change, gitRepoFiles);
-        log.debug("Initializing FunctionLocator");
+        log.debug("Initializing CallableLocator for Java projects");
         languageModuleExtension = JAVA_MODULE_EXTENSION;
+        importPattern = Pattern.compile(
+                String.format("^import\\s+(?:static\\s+)?(%s)", DOT_NOTATION_REGEX),
+                Pattern.MULTILINE
+        );
     }
 
     @Override
@@ -36,33 +33,4 @@
                 "\\s*\\(.*?\\)" +  // Parameters
                 "(?:\\s*throws\\s+[^\\{;]+)?";  // Optional throws clause
     }
-
-    @Override
-    protected String findImportedFunctionDefinition(String functionName, String content) {
-        parseImportStatements(content);
-        retrievePackageModules();
-
-        return findInModules(functionName);
-    }
-
-    private void retrievePackageModules() {
-        log.debug("Retrieving modules from current Package");
-        List<String> packageModules = codeFileFetcher.getFilesInDir(rootFileDir)
-                .stream()
-                .map(FileUtils::removeExtension)
-                .toList();
-        log.debug("Modules retrieved from current Package: {}", packageModules);
-        importModules.addAll(packageModules);
-    }
-
-    private void parseImportStatements(String content) {
-        log.debug("Parsing import statements");
-        Matcher importMatcher = IMPORT_PATTERN.matcher(content);
-        while (importMatcher.find()) {
-            String importModulesGroup = importMatcher.group(1);
-            log.debug("Parsing IMPORT module: `{}`", importModulesGroup);
-            importModules.add(importModulesGroup);
-        }
-        log.debug("Found importModules from import statements: {}", importModules);
-    }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/code/context/ondemand/locator/language/kotlin/CallableLocator.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/code/context/ondemand/locator/language/kotlin/CallableLocator.java
new file mode 100644
index 0000000..384d6f2
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/code/context/ondemand/locator/language/kotlin/CallableLocator.java
@@ -0,0 +1,48 @@
+package com.googlesource.gerrit.plugins.chatgpt.mode.stateful.client.code.context.ondemand.locator.language.kotlin;
+
+import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
+import com.googlesource.gerrit.plugins.chatgpt.interfaces.mode.stateful.client.code.context.ondemand.IEntityLocator;
+import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.api.gerrit.GerritChange;
+import com.googlesource.gerrit.plugins.chatgpt.mode.stateful.client.api.git.GitRepoFiles;
+import com.googlesource.gerrit.plugins.chatgpt.mode.stateful.client.code.context.ondemand.locator.CallableLocatorJVM;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.regex.Pattern;
+
+@Slf4j
+public class CallableLocator extends CallableLocatorJVM implements IEntityLocator {
+    private static final String KOTLIN_MODULE_EXTENSION = ".kt";
+    private static final String ALTERNATIVE_BASE_PATH = "app/src/main/kotlin/";
+    private static final String NON_MODIFIABLE_BASE_PATH = "app/src/";
+
+    public CallableLocator(Configuration config, GerritChange change, GitRepoFiles gitRepoFiles) {
+        super(config, change, gitRepoFiles);
+        log.debug("Initializing CallableLocator for Kotlin projects");
+        languageModuleExtension = KOTLIN_MODULE_EXTENSION;
+        importPattern = Pattern.compile(
+                String.format("^import\\s+(%s)", DOT_NOTATION_REGEX),
+                Pattern.MULTILINE
+        );
+    }
+
+    @Override
+    protected String getFunctionRegex(String functionName) {
+        return "^\\s*(?:@\\w+(?:\\(.*?\\))?\\s*)*" +  // Optional annotations
+                "(?:\\w+\\s+)*" +                      // Optional modifiers
+                "fun\\s+" +                            // 'fun' keyword
+                Pattern.quote(functionName) +          // Function name
+                "\\s*\\(.*?\\)" +                      // Parameters
+                "(?:\\s*:\\s*\\S+)?";                  // Optional return type
+    }
+
+    @Override
+    protected void parseImportStatements(String content) {
+        parseDirectImportStatements(content, importModules);
+        importModules.addAll(importModules.stream()
+                .filter(s -> !s.startsWith(NON_MODIFIABLE_BASE_PATH))
+                .map(s -> ALTERNATIVE_BASE_PATH + s)
+                .toList()
+        );
+        retrievePackageModules();
+    }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/code/context/ondemand/locator/language/python/CallableLocator.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/code/context/ondemand/locator/language/python/CallableLocator.java
index 6766deb..276aacd 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/code/context/ondemand/locator/language/python/CallableLocator.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/code/context/ondemand/locator/language/python/CallableLocator.java
@@ -17,14 +17,6 @@
 @Slf4j
 public class CallableLocator extends CallableLocatorBase implements IEntityLocator {
     private static final String PYTHON_MODULE_EXTENSION = ".py";
-    private static final Pattern IMPORT_PATTERN = Pattern.compile(
-            String.format(
-                    "^(?:from\\s+(%1$s)\\s+import\\s+(\\*|\\w+(?:%2$s\\w+)*)|import\\s+(%1$s(?:%2$s%1$s))*)",
-                    DOT_NOTATION_REGEX,
-                    ITEM_COMMA_DELIMITED_REGEX
-            ),
-            Pattern.MULTILINE
-    );
 
     private final Map<String, String> fromModuleMap = new HashMap<>();
 
@@ -32,6 +24,14 @@
         super(config, change, gitRepoFiles);
         log.debug("Initializing CallableLocator for Python projects");
         languageModuleExtension = PYTHON_MODULE_EXTENSION;
+        importPattern = Pattern.compile(
+                String.format(
+                        "^(?:from\\s+(%1$s)\\s+import\\s+(\\*|\\w+(?:%2$s\\w+)*)|import\\s+(%1$s(?:%2$s%1$s))*)",
+                        DOT_NOTATION_REGEX,
+                        ITEM_COMMA_DELIMITED_REGEX
+                ),
+                Pattern.MULTILINE
+        );
     }
 
     @Override
@@ -43,15 +43,9 @@
     }
 
     @Override
-    protected String findImportedFunctionDefinition(String functionName, String content) {
-        parseImportStatements(content);
-
-        return findInImportModules(functionName);
-    }
-
-    private void parseImportStatements(String content) {
+    protected void parseImportStatements(String content) {
         log.debug("Parsing import statements");
-        Matcher importMatcher = IMPORT_PATTERN.matcher(content);
+        Matcher importMatcher = importPattern.matcher(content);
         while (importMatcher.find()) {
             String fromModuleGroup = importMatcher.group(1);
             String fromEntitiesGroup = importMatcher.group(2);
@@ -73,7 +67,8 @@
         log.debug("Found importModules: {}", importModules);
     }
 
-    private String findInImportModules(String functionName) {
+    @Override
+    protected String findInImportModules(String functionName) {
         // Check if the function is directly imported via `from MODULE import ENTITY`
         if (fromModuleMap.containsKey(functionName)) {
             String module = fromModuleMap.get(functionName);