Introduce `PluginDataHandlerProvider`

Transition from `PluginDataHandler` to `PluginDataHandlerProvider` to
broaden the application of persistent plugin data across different
scopes. Initially, only a Global scope is supported, but the addition of
Project and Change Set scopes is planned in future commits to support
ongoing developments and issue resolutions.

Change-Id: I1ed78760a6a86b5df89c27a1823b1940acd27b89
Signed-off-by: Patrizio <patrizio.gelosi@amarulasolutions.com>
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/data/PluginDataHandler.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/data/PluginDataHandler.java
index 09cfba1..c3f92dc 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/data/PluginDataHandler.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/data/PluginDataHandler.java
@@ -14,8 +14,8 @@
     private final Properties configProperties = new Properties();
 
     @Inject
-    public PluginDataHandler(@com.google.gerrit.extensions.annotations.PluginData Path pluginDataDir) {
-        this.configFile = pluginDataDir.resolve("plugin.config");
+    public PluginDataHandler(Path configFilePath) {
+        this.configFile = configFilePath;
         try {
             loadOrCreateProperties();
         } catch (IOException e) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/data/PluginDataHandlerProvider.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/data/PluginDataHandlerProvider.java
new file mode 100644
index 0000000..f7366ca
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/data/PluginDataHandlerProvider.java
@@ -0,0 +1,26 @@
+package com.googlesource.gerrit.plugins.chatgpt.data;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import java.nio.file.Path;
+
+@Singleton
+public class PluginDataHandlerProvider implements Provider<PluginDataHandler> {
+    private final Path defaultPluginDataPath;
+
+    @Inject
+    public PluginDataHandlerProvider(@com.google.gerrit.extensions.annotations.PluginData Path defaultPluginDataPath) {
+        this.defaultPluginDataPath = defaultPluginDataPath;
+    }
+
+    public PluginDataHandler get(Path configPath) {
+        return new PluginDataHandler(configPath);
+    }
+
+    @Override
+    public PluginDataHandler get() {
+        return get(defaultPluginDataPath.resolve("plugin.config"));
+    }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/GerritEventContextModule.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/GerritEventContextModule.java
index 0d76a17..4384b40 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/GerritEventContextModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/GerritEventContextModule.java
@@ -2,8 +2,11 @@
 
 import com.google.gerrit.extensions.config.FactoryModule;
 import com.google.gerrit.server.events.Event;
+import com.google.inject.Singleton;
 import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
 import com.googlesource.gerrit.plugins.chatgpt.data.ChangeSetDataProvider;
+import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandler;
+import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandlerProvider;
 import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.api.gerrit.GerritChange;
 import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.data.ChangeSetData;
 import com.googlesource.gerrit.plugins.chatgpt.mode.interfaces.client.api.chatgpt.IChatGptClient;
@@ -32,6 +35,7 @@
         bind(Configuration.class).toInstance(config);
         bind(GerritChange.class).toInstance(new GerritChange(event));
         bind(ChangeSetData.class).toProvider(ChangeSetDataProvider.class).in(SINGLETON);
+        bind(PluginDataHandler.class).toProvider(PluginDataHandlerProvider.class).in(Singleton.class);
     }
 
     private Class<? extends IChatGptClient> getChatGptMode() {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptAssistant.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptAssistant.java
index 2fd2771..7fc146c 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptAssistant.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptAssistant.java
@@ -2,6 +2,7 @@
 
 import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
 import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandler;
+import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandlerProvider;
 import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.ClientBase;
 import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.api.chatgpt.ChatGptParameters;
 import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.api.chatgpt.ChatGptTools;
@@ -30,12 +31,16 @@
     private final GitRepoFiles gitRepoFiles;
     private final PluginDataHandler pluginDataHandler;
 
-    public ChatGptAssistant(Configuration config, GerritChange change, GitRepoFiles gitRepoFiles,
-                            PluginDataHandler pluginDataHandler) {
+    public ChatGptAssistant(
+            Configuration config,
+            GerritChange change,
+            GitRepoFiles gitRepoFiles,
+            PluginDataHandlerProvider pluginDataHandlerProvider
+    ) {
         super(config);
         this.change = change;
         this.gitRepoFiles = gitRepoFiles;
-        this.pluginDataHandler = pluginDataHandler;
+        this.pluginDataHandler = pluginDataHandlerProvider.get();
     }
 
     public void setupAssistant() {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptClientStateful.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptClientStateful.java
index 6e5944d..5d652bb 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptClientStateful.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptClientStateful.java
@@ -4,7 +4,7 @@
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
-import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandler;
+import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandlerProvider;
 import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.api.chatgpt.ChatGptClient;
 import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.api.gerrit.GerritChange;
 import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.api.chatgpt.ChatGptResponseContent;
@@ -15,13 +15,13 @@
 @Slf4j
 @Singleton
 public class ChatGptClientStateful extends ChatGptClient implements IChatGptClient {
-    private final PluginDataHandler pluginDataHandler;
+    private final PluginDataHandlerProvider pluginDataHandlerProvider;
 
     @VisibleForTesting
     @Inject
-    public ChatGptClientStateful(PluginDataHandler pluginDataHandler) {
+    public ChatGptClientStateful(PluginDataHandlerProvider pluginDataHandlerProvider) {
         super();
-        this.pluginDataHandler = pluginDataHandler;
+        this.pluginDataHandlerProvider = pluginDataHandlerProvider;
     }
 
     public ChatGptResponseContent ask(Configuration config, ChangeSetData changeSetData, GerritChange change, String patchSet) {
@@ -33,7 +33,7 @@
         String threadId = chatGptThread.createThread();
         chatGptThread.addMessage();
 
-        ChatGptRun chatGptRun = new ChatGptRun(threadId, config, pluginDataHandler);
+        ChatGptRun chatGptRun = new ChatGptRun(threadId, config, pluginDataHandlerProvider);
         chatGptRun.createRun();
         chatGptRun.pollRun();
         // Attribute `requestBody` is valued for testing purposes
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptRun.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptRun.java
index 9be6680..466afa1 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptRun.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptRun.java
@@ -2,6 +2,7 @@
 
 import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
 import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandler;
+import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandlerProvider;
 import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.ClientBase;
 import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.api.chatgpt.ChatGptToolCall;
 import com.googlesource.gerrit.plugins.chatgpt.mode.stateful.client.api.UriResourceLocatorStateful;
@@ -32,10 +33,10 @@
     private ChatGptResponse runResponse;
     private ChatGptListResponse stepResponse;
 
-    public ChatGptRun(String threadId, Configuration config, PluginDataHandler pluginDataHandler) {
+    public ChatGptRun(String threadId, Configuration config, PluginDataHandlerProvider pluginDataHandlerProvider) {
         super(config);
         this.threadId = threadId;
-        this.pluginDataHandler = pluginDataHandler;
+        this.pluginDataHandler = pluginDataHandlerProvider.get();
     }
 
     public void createRun() {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/gerrit/GerritClientPatchSetStateful.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/gerrit/GerritClientPatchSetStateful.java
index 502bac0..7c66e56 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/gerrit/GerritClientPatchSetStateful.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/gerrit/GerritClientPatchSetStateful.java
@@ -5,7 +5,7 @@
 import com.google.gerrit.server.util.ManualRequestContext;
 import com.google.inject.Inject;
 import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
-import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandler;
+import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandlerProvider;
 import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.data.ChangeSetData;
 import com.googlesource.gerrit.plugins.chatgpt.mode.stateful.client.api.chatgpt.ChatGptAssistant;
 import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.api.gerrit.GerritChange;
@@ -28,7 +28,7 @@
             Pattern.MULTILINE);
 
     private final GitRepoFiles gitRepoFiles;
-    private final PluginDataHandler pluginDataHandler;
+    private final PluginDataHandlerProvider pluginDataHandlerProvider;
 
     private GerritChange change;
 
@@ -38,15 +38,15 @@
             Configuration config,
             AccountCache accountCache,
             GitRepoFiles gitRepoFiles,
-            PluginDataHandler pluginDataHandler) {
+            PluginDataHandlerProvider pluginDataHandlerProvider) {
         super(config, accountCache);
         this.gitRepoFiles = gitRepoFiles;
-        this.pluginDataHandler = pluginDataHandler;
+        this.pluginDataHandlerProvider = pluginDataHandlerProvider;
     }
 
     public String getPatchSet(ChangeSetData changeSetData, GerritChange change) throws Exception {
         this.change = change;
-        ChatGptAssistant chatGptAssistant = new ChatGptAssistant(config, change, gitRepoFiles, pluginDataHandler);
+        ChatGptAssistant chatGptAssistant = new ChatGptAssistant(config, change, gitRepoFiles, pluginDataHandlerProvider);
         chatGptAssistant.setupAssistant();
 
         String formattedPatch = getPatchFromGerrit();
diff --git a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewStatefulTest.java b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewStatefulTest.java
index 3be3d8f..02f16c5 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewStatefulTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewStatefulTest.java
@@ -8,6 +8,7 @@
 import com.google.gerrit.extensions.restapi.BinaryResult;
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gson.JsonObject;
+import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandler;
 import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.api.chatgpt.ChatGptResponseContent;
 import com.googlesource.gerrit.plugins.chatgpt.mode.stateful.client.api.UriResourceLocatorStateful;
 import com.googlesource.gerrit.plugins.chatgpt.mode.stateful.client.prompt.ChatGptPromptStateful;
@@ -19,6 +20,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.mockito.junit.MockitoJUnitRunner;
@@ -49,6 +51,9 @@
     private ChatGptPromptStateful chatGptPromptStateful;
     private JsonObject threadMessage;
 
+    @Mock
+    protected PluginDataHandler pluginDataHandler;
+
     public ChatGptReviewStatefulTest() {
         MockitoAnnotations.openMocks(this);
     }
@@ -59,6 +64,9 @@
         // Mock the Global Config values that differ from the ones provided by Default
         when(globalConfig.getString(Mockito.eq("gptMode"), Mockito.anyString()))
                 .thenReturn(MODES.stateful.name());
+
+        // Mock the pluginDataHandlerProvider to return the mocked pluginDataHandler
+        when(pluginDataHandlerProvider.get()).thenReturn(pluginDataHandler);
     }
 
     protected void initConfig() {
diff --git a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewTestBase.java b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewTestBase.java
index fd316e0..393a87f 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewTestBase.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewTestBase.java
@@ -29,13 +29,12 @@
 import com.google.gson.JsonObject;
 import com.google.inject.AbstractModule;
 import com.google.inject.Guice;
-import com.googlesource.gerrit.plugins.chatgpt.config.ConfigCreator;
-import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
-import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandler;
-import com.googlesource.gerrit.plugins.chatgpt.listener.EventHandlerTask;
-import com.googlesource.gerrit.plugins.chatgpt.listener.GerritEventContextModule;
 import com.google.inject.TypeLiteral;
 import com.google.inject.util.Providers;
+import com.googlesource.gerrit.plugins.chatgpt.config.ConfigCreator;
+import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
+import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandlerProvider;
+import com.googlesource.gerrit.plugins.chatgpt.listener.EventHandlerTask;
 import com.googlesource.gerrit.plugins.chatgpt.localization.Localizer;
 import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.api.gerrit.GerritChange;
 import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.api.gerrit.GerritClient;
@@ -102,7 +101,7 @@
     protected GitRepoFiles gitRepoFiles;
 
     @Mock
-    protected PluginDataHandler pluginDataHandler;
+    protected PluginDataHandlerProvider pluginDataHandlerProvider;
 
     @Mock
     protected OneOffRequestContext context;
@@ -278,13 +277,13 @@
         EventHandlerTask task = Guice.createInjector(new AbstractModule() {
             @Override
             protected void configure() {
-                install(new GerritEventContextModule(config, event));
+                install(new TestGerritEventContextModule(config, event));
 
                 bind(GerritClient.class).toInstance(gerritClient);
                 bind(GitRepoFiles.class).toInstance(gitRepoFiles);
                 bind(ConfigCreator.class).toInstance(mockConfigCreator);
                 bind(PatchSetReviewer.class).toInstance(patchSetReviewer);
-                bind(PluginDataHandler.class).toInstance(pluginDataHandler);
+                bind(PluginDataHandlerProvider.class).toInstance(pluginDataHandlerProvider);
                 bind(AccountCache.class).toInstance(mockAccountCache());
             }
         }).getInstance(EventHandlerTask.class);
@@ -378,14 +377,14 @@
 
     private IChatGptClient getChatGptClient() {
         return switch (config.getGptMode()) {
-            case stateful -> new ChatGptClientStateful(pluginDataHandler);
+            case stateful -> new ChatGptClientStateful(pluginDataHandlerProvider);
             case stateless -> new ChatGptClientStateless();
         };
     }
 
     private IGerritClientPatchSet getGerritClientPatchSet() {
         return switch (config.getGptMode()) {
-            case stateful -> new GerritClientPatchSetStateful(config, accountCacheMock, gitRepoFiles, pluginDataHandler);
+            case stateful -> new GerritClientPatchSetStateful(config, accountCacheMock, gitRepoFiles, pluginDataHandlerProvider);
             case stateless -> new GerritClientPatchSetStateless(config, accountCacheMock);
         };
     }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/PluginDataTest.java b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/PluginDataTest.java
index 0823fec..5bdc08f 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/PluginDataTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/PluginDataTest.java
@@ -7,6 +7,7 @@
 import java.nio.file.Path;
 
 import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandler;
+import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandlerProvider;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -36,7 +37,8 @@
 
     @Test
     public void testValueSetAndGet() {
-        PluginDataHandler handler = new PluginDataHandler(mockPluginDataPath);
+        PluginDataHandlerProvider provider = new PluginDataHandlerProvider(mockPluginDataPath);
+        PluginDataHandler handler = provider.get();
 
         String key = "testKey";
         String value = "testValue";
@@ -50,7 +52,8 @@
 
     @Test
     public void testRemoveValue() {
-        PluginDataHandler handler = new PluginDataHandler(mockPluginDataPath);
+        PluginDataHandlerProvider provider = new PluginDataHandlerProvider(mockPluginDataPath);
+        PluginDataHandler handler = provider.get();
 
         String key = "testKey";
         String value = "testValue";
@@ -69,7 +72,8 @@
         // Ensure the file doesn't exist before creating the handler
         Files.deleteIfExists(realPluginDataPath);
 
-        new PluginDataHandler(mockPluginDataPath);
+        PluginDataHandlerProvider provider = new PluginDataHandlerProvider(mockPluginDataPath);
+        provider.get();
 
         // The constructor should create the file if it doesn't exist
         assertTrue("The config file should exist after initializing the handler.", Files.exists(realPluginDataPath));
diff --git a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/TestGerritEventContextModule.java b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/TestGerritEventContextModule.java
new file mode 100644
index 0000000..7506e9a
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/TestGerritEventContextModule.java
@@ -0,0 +1,23 @@
+package com.googlesource.gerrit.plugins.chatgpt;
+
+import com.google.gerrit.extensions.annotations.PluginData;
+import com.google.gerrit.server.events.Event;
+import com.google.inject.Provides;
+import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
+import com.googlesource.gerrit.plugins.chatgpt.listener.GerritEventContextModule;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+public class TestGerritEventContextModule extends GerritEventContextModule {
+
+    public TestGerritEventContextModule(Configuration config, Event event) {
+        super(config, event);
+    }
+
+    @Provides
+    @PluginData
+    Path providePluginDataPath() {
+        return Paths.get(System.getProperty("pluginDataPath", "test-plugin-data"));
+    }
+}