Add context locator for Java function definitions
Implemented Java-specific context locator to handle function definition
requests in Java projects.
Change-Id: Ib384e68aa98813d6b152f795136d9a9abadc2b5d
Signed-off-by: Patrizio <patrizio.gelosi@amarulasolutions.com>
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptRepoUploader.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptRepoUploader.java
index 45f6d9c..f2ac692 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptRepoUploader.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptRepoUploader.java
@@ -34,7 +34,7 @@
public List<String> uploadRepoFiles() throws OpenAiConnectionFailException {
log.debug("Starting uploading repository files.");
- List<String> repoFiles = gitRepoFiles.getGitRepoFiles(config, change);
+ List<String> repoFiles = gitRepoFiles.getGitRepoFilesAsJson(config, change);
List<String> filesIds = new ArrayList<>();
filenameBase = sanitizeFilename(change.getProjectName());
filenameIndex = 0;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/git/GitRepoFiles.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/git/GitRepoFiles.java
index 43aabfc..25b8b6b 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/git/GitRepoFiles.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/git/GitRepoFiles.java
@@ -10,6 +10,7 @@
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.filter.PathFilter;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
import java.io.File;
@@ -30,7 +31,8 @@
private List<String> enabledFileExtensions;
private long fileSize;
- public List<String> getGitRepoFiles(Configuration config, GerritChange change) {
+ public List<String> getGitRepoFilesAsJson(Configuration config, GerritChange change) {
+ log.debug("Getting Repository files as JSON");
gitFileChunkBuilder = new GitFileChunkBuilder(config);
enabledFileExtensions = config.getEnabledFileExtensions();
try (Repository repository = openRepository(change)) {
@@ -43,6 +45,18 @@
}
}
+ public List<FileEntry> getDirFiles(Configuration config, GerritChange change, String path) {
+ log.debug("Getting files from selected directory");
+ enabledFileExtensions = config.getEnabledFileExtensions();
+ try (Repository repository = openRepository(change)) {
+ Map<String, List<FileEntry>> dirFilesMap = getDirFilesMap(repository, PathFilter.create(path));
+ log.debug("Retrieved file directories: {}", dirFilesMap.keySet());
+ return dirFilesMap.get(path);
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to retrieve files in path " + path, e);
+ }
+ }
+
public String getFileContent(GerritChange change, String path) throws IOException {
try (Repository repository = openRepository(change);
ObjectReader reader = repository.newObjectReader()) {
@@ -59,6 +73,18 @@
}
private List<Map<String, String>> listFilesWithContent(Repository repository) throws IOException, GitAPIException {
+ Map<String, List<FileEntry>> dirFilesMap = getDirFilesMap(repository, TreeFilter.ANY_DIFF);
+ for (Map.Entry<String, List<FileEntry>> entry : dirFilesMap.entrySet()) {
+ String dirPath = entry.getKey();
+ log.debug("File from dirFilesMap processed: {}", dirPath);
+ List<FileEntry> fileEntries = entry.getValue();
+ gitFileChunkBuilder.addFiles(dirPath, fileEntries);
+ }
+
+ return gitFileChunkBuilder.getChunks();
+ }
+
+ private Map<String, List<FileEntry>> getDirFilesMap(Repository repository, TreeFilter filter) throws IOException {
Map<String, List<FileEntry>> dirFilesMap = new LinkedHashMap<>();
try (ObjectReader reader = repository.newObjectReader()) {
@@ -67,7 +93,7 @@
try (TreeWalk treeWalk = new TreeWalk(repository)) {
treeWalk.addTree(tree);
treeWalk.setRecursive(true);
- treeWalk.setFilter(TreeFilter.ANY_DIFF);
+ treeWalk.setFilter(filter);
while (treeWalk.next()) {
String path = treeWalk.getPathString();
@@ -82,14 +108,7 @@
}
}
}
- for (Map.Entry<String, List<FileEntry>> entry : dirFilesMap.entrySet()) {
- String dirPath = entry.getKey();
- log.debug("File from dirFilesMap processed: {}", dirPath);
- List<FileEntry> fileEntries = entry.getValue();
- gitFileChunkBuilder.addFiles(dirPath, fileEntries);
- }
-
- return gitFileChunkBuilder.getChunks();
+ return dirFilesMap;
}
private Repository openRepository(GerritChange change) throws IOException {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/code/context/ondemand/CodeFileFetcher.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/code/context/ondemand/CodeFileFetcher.java
index 9bf0b86..520ee2d 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/code/context/ondemand/CodeFileFetcher.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/code/context/ondemand/CodeFileFetcher.java
@@ -4,9 +4,14 @@
import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.ClientBase;
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.model.api.git.FileEntry;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
@Slf4j
public class CodeFileFetcher extends ClientBase {
@@ -14,6 +19,7 @@
private final GitRepoFiles gitRepoFiles;
private String basePathRegEx = "";
+ private Map<String, String> preloadedFiles = new LinkedHashMap<>();
public CodeFileFetcher(Configuration config, GerritChange change, GitRepoFiles gitRepoFiles) {
super(config);
@@ -25,9 +31,19 @@
}
public String getFileContent(String filename) throws IOException {
+ if (preloadedFiles.containsKey(filename)) {
+ return preloadedFiles.get(filename);
+ }
if (!basePathRegEx.isEmpty()) {
filename = filename.replaceAll(basePathRegEx, "");
}
return gitRepoFiles.getFileContent(change, filename);
}
+
+ public Set<String> getFilesInDir(String dirname) {
+ preloadedFiles = gitRepoFiles.getDirFiles(config, change, dirname)
+ .stream()
+ .collect(Collectors.toMap(FileEntry::getPath, FileEntry::getContent));
+ return preloadedFiles.keySet();
+ }
}
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
new file mode 100644
index 0000000..b02aec6
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/code/context/ondemand/locator/language/java/CallableLocator.java
@@ -0,0 +1,68 @@
+package com.googlesource.gerrit.plugins.chatgpt.mode.stateful.client.code.context.ondemand.locator.language.java;
+
+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.CallableLocatorBase;
+import com.googlesource.gerrit.plugins.chatgpt.utils.FileUtils;
+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 {
+ 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");
+ languageModuleExtension = JAVA_MODULE_EXTENSION;
+ }
+
+ @Override
+ protected String getFunctionRegex(String functionName) {
+ return "^\\s*(?:@\\w+(?:\\(.*?\\))?\\s*)*" + // Optional annotations
+ "(?:(?:public|protected|private|static|final|abstract|synchronized|native|strictfp)\\s+)*" + // Optional modifiers
+ "(?:<[^>]+>\\s*)?" + // Optional type parameters
+ "\\S+\\s+" + // Return type
+ Pattern.quote(functionName) + // Method name
+ "\\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/utils/FileUtils.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/utils/FileUtils.java
index 994f247..edabe47 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/utils/FileUtils.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/utils/FileUtils.java
@@ -61,6 +61,14 @@
return filename.substring(lastDotIndex + 1);
}
+ public static String removeExtension(String filename) {
+ int lastDotIndex = filename.lastIndexOf('.');
+ if (lastDotIndex <= 0 || lastDotIndex == filename.length() - 1) {
+ return filename;
+ }
+ return filename.substring(0, lastDotIndex);
+ }
+
public static String getDirName(String filename) {
return Optional.ofNullable(new File(filename).getParent()).orElse("");
}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewStatefulTestBase.java b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewStatefulTestBase.java
index cc09f67..6ab42ca 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewStatefulTestBase.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewStatefulTestBase.java
@@ -81,7 +81,7 @@
// Mock the behavior of the Git Repository Manager
String repoJson = readTestFile("__files/stateful/gitProjectFiles.json");
- when(gitRepoFiles.getGitRepoFiles(any(), any())).thenReturn(List.of(repoJson));
+ when(gitRepoFiles.getGitRepoFilesAsJson(any(), any())).thenReturn(List.of(repoJson));
// Mock the behavior of the ChatGPT create-file request
WireMock.stubFor(WireMock.post(WireMock.urlEqualTo(UriResourceLocatorStateful.filesCreateUri()))