Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/amir10ea_ai-code-assistant.json
T

1 line
122 KiB
JSON

{"repo": "amir10ea/ai-code-assistant", "files": {"src/main/java/com/examensarbete/AIClient.java": {"d52233effad937f96b022d98bf8efbe98fbbe8b4": {"t": 1785746134.0504215, "n": 15327, "h": "c519bf0f82272a84", "c": "package com.examensarbete;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Properties;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\npublic class AIClient {\n private static final ObjectMapper JSON_MAPPER = new ObjectMapper();\n private static final Properties API_KEYS = loadApiKeys();\n \n // Enhanced regex with flexible whitespace handling\n private static final Pattern BUG_BLOCK_PATTERN = Pattern.compile(\n \"BUG\\\\s+LOCATION:\\\\s*(.+?)\\\\n\" +\n \"BUG\\\\s+TYPE:\\\\s*(.+?)\\\\n\" +\n \"EXPLANATION:\\\\s*([\\\\s\\\\S]*?)\\\\n\" +\n \"ORIGINAL\\\\s+CODE:\\\\s*```java\\\\s*([\\\\s\\\\S]*?)```\\\\s*\\\\n\" +\n \"CORRECTED\\\\s+CODE:\\\\s*```java\\\\s*([\\\\s\\\\S]*?)```\",\n Pattern.CASE_INSENSITIVE | Pattern.DOTALL\n );\n\n private static Properties loadApiKeys() {\n Properties properties = new Properties();\n try (InputStream in = AIClient.class.getClassLoader().getResourceAsStream(\"api-keys.properties\")) {\n if (in != null) {\n properties.load(in);\n }\n } catch (IOException e) {\n // Silent fail - will try environment variables\n }\n return properties;\n }\n\n public static String sendRequest(String model, String prompt, boolean verbose) throws Exception {\n if (verbose) {\n System.out.println(\"\\n=== SENDING REQUEST ===\");\n System.out.println(\"[DEBUG] Model: \" + model);\n System.out.println(\"[DEBUG] Prompt:\\n\" + prompt);\n }\n\n String apiKey = getApiKey(model);\n String apiUrl = getApiUrl(model);\n\n HttpClient client = HttpClient.newHttpClient();\n String requestBody = buildRequestBody(model, prompt);\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(apiUrl))\n .header(\"Content-Type\", \"application/json\")\n .header(model.toLowerCase().equals(\"claude\") ? \"x-api-key\" : \"Authorization\", \n model.toLowerCase().equals(\"claude\") ? apiKey : \"Bearer \" + apiKey)\n .header(\"anthropic-version\", \"2023-06-01\")\n .POST(HttpRequest.BodyPublishers.ofString(requestBody))\n .build();\n\n HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());\n \n if (verbose) {\n System.out.println(\"\\n=== RAW RESPONSE ===\");\n System.out.println(\"[DEBUG] Status Code: \" + response.statusCode());\n System.out.println(\"[DEBUG] Response Body:\\n\" + response.body());\n }\n \n return response.body();\n }\n\n public static AIResponse parseResponse(String response, boolean verbose) throws Exception {\n if (verbose) {\n System.out.println(\"\\n=== PARSING RESPONSE ===\");\n }\n \n String content = extractContentFromApiResponse(response, verbose);\n \n if (content == null || content.trim().isEmpty()) {\n System.err.println(\"[ERROR] Empty or null content in response\");\n return null;\n }\n\n if (verbose) {\n System.out.println(\"[DEBUG] Extracted Content:\\n\" + content);\n }\n \n List<AIResponse.BugFix> bugFixes = new ArrayList<>();\n Matcher matcher = BUG_BLOCK_PATTERN.matcher(content);\n int blockCount = 0;\n \n while (matcher.find()) {\n blockCount++;\n try {\n String bugPosition = matcher.group(1).trim();\n String bugType = matcher.group(2).trim();\n String explanation = matcher.group(3).trim();\n String originalCode = matcher.group(4).trim();\n String correctedCode = matcher.group(5).trim();\n\n // Basic validation\n if (correctedCode.isEmpty() || originalCode.isEmpty()) {\n if (verbose) {\n System.err.println(\"[WARN] Skipping block \" + blockCount + \": Empty code snippet detected\");\n }\n continue;\n }\n\n AIResponse.BugFix bugFix = new AIResponse.BugFix(\n bugPosition, \n correctedCode,\n bugType,\n explanation,\n originalCode\n );\n bugFixes.add(bugFix);\n \n if (verbose) {\n System.out.println(\"[DEBUG] Parsed Bug Fix \" + blockCount + \":\");\n System.out.println(\"- Position: \" + bugPosition);\n System.out.println(\"- Type: \" + bugType);\n System.out.println(\"- Explanation: \" + explanation);\n }\n \n } catch (Exception e) {\n System.err.println(\"[ERROR] Failed to parse bug block \" + blockCount + \": \" + e.getMessage());\n }\n }\n\n // Extract complete file\n String completeFile = null;\n Pattern completeFilePattern = Pattern.compile(\n \"COMPLETE\\\\s+FILE:\\\\s*```java\\\\s*([\\\\s\\\\S]*?)```\",\n Pattern.CASE_INSENSITIVE | Pattern.DOTALL\n );\n Matcher completeFileMatcher = completeFilePattern.matcher(content);\n if (completeFileMatcher.find()) {\n completeFile = completeFileMatcher.group(1).trim();\n }\n\n if (!bugFixes.isEmpty()) {\n if (verbose) {\n System.out.println(\"[INFO] Successfully parsed \" + bugFixes.size() + \" bug fixes out of \" + blockCount + \" blocks\");\n }\n return new AIResponse(bugFixes, completeFile);\n }\n \n if (verbose) {\n System.out.println(\"[ERROR] Failed to parse any bug fixes from \" + blockCount + \" potential blocks\");\n System.out.println(\"[DEBUG] Full content for analysis:\\n\" + content);\n }\n return null;\n }\n\n private static String extractContentFromApiResponse(String response, boolean verbose) throws Exception {\n if (verbose) {\n System.out.println(\"\\n=== EXTRACTING CONTENT ===\");\n }\n \n try {\n JsonNode root = JSON_MAPPER.readTree(response);\n if (verbose) {\n System.out.println(\"[DEBUG] JSON Structure:\\n\" + root.toPrettyString());\n }\n\n if (root.has(\"choices\")) {\n JsonNode choice = root.get(\"choices\").get(0);\n if (choice.has(\"message\") && choice.get(\"message\").has(\"content\")) {\n String content = choice.get(\"message\").get(\"content\").asText();\n if (verbose) {\n System.out.println(\"[DEBUG] Extracted OpenAI content\");\n }\n return content;\n }\n }\n\n if (root.has(\"content\")) {\n StringBuilder contentBuilder = new StringBuilder();\n for (JsonNode item : root.get(\"content\")) {\n if (item.has(\"text\")) {\n contentBuilder.append(item.get(\"text\").asText());\n }\n }\n if (verbose) {\n System.out.println(\"[DEBUG] Extracted Anthropic content\");\n }\n return contentBuilder.toString();\n }\n\n if (root.has(\"response\")) {\n if (verbose) {\n System.out.println(\"[DEBUG] Extracted DeepSeek content\");\n }\n return root.get(\"response\").asText();\n }\n\n if (verbose) {\n System.out.println(\"[WARN] Unknown response format, returning raw response\");\n }\n return response;\n \n } catch (Exception e) {\n System.err.println(\"[ERROR] Failed to parse JSON: \" + e.getMessage());\n if (verbose) {\n System.out.println(\"[DEBUG] Raw response:\\n\" + response);\n }\n return response;\n }\n }\n\n private static String getApiKey(String model) {\n String keyName = model.toUpperCase() + \"_API_KEY\";\n String apiKey = API_KEYS.getProperty(keyName, System.getenv(keyName));\n \n if (apiKey == null || apiKey.isBlank()) {\n throw new RuntimeException(\"Missing API key for \" + model);\n }\n return apiKey;\n }\n\n private static String getApiUrl(String model) {\n return switch (model.toLowerCase()) {\n case \"openai\" -> \"https://api.openai.com/v1/chat/completions\";\n case \"claude\" -> \"https://api.anthropic.com/v1/messages\";\n case \"deepseek\" -> \"https://api.deepseek.com/v1/chat/completions\";\n default -> throw new IllegalArgumentException(\"Unsupported model: \" + model);\n };\n }\n\n private static String buildRequestBody(String model, String prompt) throws Exception {\n return switch (model.toLowerCase()) {\n case \"openai\" -> JSON_MAPPER.writeValueAsString(new OpenAIRequest(prompt));\n case \"claude\" -> JSON_MAPPER.writeValueAsString(new ClaudeRequest(prompt));\n case \"deepseek\" -> JSON_MAPPER.writeValueAsString(new DeepSeekRequest(prompt));\n default -> throw new IllegalArgumentException(\"Unsupported model: \" + model);\n };\n }\n\n public static class AIResponse {\n private final List<BugFix> bugFixes;\n private final String completeFile;\n \n public AIResponse(List<BugFix> bugFixes, String completeFile) {\n this.bugFixes = bugFixes;\n this.completeFile = completeFile;\n }\n\n public List<BugFix> getBugFixes() { \n return bugFixes; \n }\n\n public String getCompleteFile() {\n return completeFile;\n }\n\n public void printBugSummary() {\n // First, collect all bug locations\n StringBuilder locations = new StringBuilder(\"Bug locations: \");\n for (int i = 0; i < bugFixes.size(); i++) {\n BugFix bugFix = bugFixes.get(i);\n String[] positionParts = bugFix.getBugPosition().split(\":\");\n if (positionParts.length >= 2) {\n String lineNumbers = positionParts[1].trim();\n // Remove any file name that might have been included in the line numbers\n lineNumbers = lineNumbers.replaceAll(\"\\\\s*,\\\\s*[^0-9-]+\", \"\");\n locations.append(lineNumbers);\n if (i < bugFixes.size() - 1) {\n locations.append(\", \");\n }\n }\n }\n System.out.println(\"\\n\" + locations.toString());\n \n // Then show each bug's details\n System.out.println(\"\\nBug Details:\");\n for (BugFix bugFix : bugFixes) {\n String[] positionParts = bugFix.getBugPosition().split(\":\");\n if (positionParts.length >= 2) {\n String fileName = positionParts[0].trim();\n String lineNumbers = positionParts[1].trim();\n // Clean up the line numbers\n lineNumbers = lineNumbers.replaceAll(\"\\\\s*,\\\\s*[^0-9-]+\", \"\");\n System.out.println(\"Location: \" + fileName + \":\" + lineNumbers);\n System.out.println(\"Type: \" + bugFix.getBugType());\n System.out.println(\"Explanation: \" + bugFix.getExplanation());\n System.out.println();\n }\n }\n \n // Show the proposed changes\n System.out.println(\"Proposed Changes:\");\n String fileName = bugFixes.get(0).getBugPosition().split(\":\")[0].trim();\n System.out.println(\"file: \" + fileName);\n System.out.println(\"<<<<<<< SEARCH\");\n for (BugFix bugFix : bugFixes) {\n System.out.println(bugFix.getOriginalCode());\n }\n System.out.println(\"=======\");\n for (BugFix bugFix : bugFixes) {\n System.out.println(bugFix.getCorrectedCode());\n }\n System.out.println(\">>>>>>> REPLACE\");\n }\n\n public static class BugFix {\n private final String bugPosition;\n private final String correctedCode;\n private final String bugType;\n private final String explanation;\n private final String originalCode;\n \n public BugFix(String bugPosition, String correctedCode, \n String bugType, String explanation, String originalCode) {\n this.bugPosition = bugPosition;\n this.correctedCode = correctedCode;\n this.bugType = bugType;\n this.explanation = explanation;\n this.originalCode = originalCode;\n }\n\n public String getBugPosition() { return bugPosition; }\n public String getCorrectedCode() { return correctedCode; }\n public String getBugType() { return bugType; }\n public String getExplanation() { return explanation; }\n public String getOriginalCode() { return originalCode; }\n }\n }\n\n private static class OpenAIRequest {\n public String model = \"gpt-4.1\";\n public List<Message> messages;\n\n public OpenAIRequest(String prompt) {\n this.messages = List.of(new Message(\"user\", prompt));\n }\n\n static class Message {\n public String role;\n public String content;\n public Message(String role, String content) {\n this.role = role;\n this.content = content;\n }\n }\n }\n\n private static class ClaudeRequest {\n public String model = \"claude-3-7-sonnet-20250219\";\n public int max_tokens = 8192;\n public List<Message> messages;\n\n public ClaudeRequest(String prompt) {\n this.messages = List.of(new Message(prompt));\n }\n\n static class Message {\n public String role = \"user\";\n public List<Content> content;\n public Message(String prompt) {\n this.content = List.of(new Content(prompt));\n }\n static class Content {\n public String type = \"text\";\n public String text;\n public Content(String text) {\n this.text = text;\n }\n }\n }\n }\n\n private static class DeepSeekRequest {\n public String model = \"deepseek-reasoner\";\n public int max_tokens = 8192;\n public List<Message> messages;\n\n public DeepSeekRequest(String prompt) {\n this.messages = List.of(new Message(\"user\", prompt));\n }\n\n static class Message {\n public String role;\n public String content;\n public Message(String role, String content) {\n this.role = role;\n this.content = content;\n }\n }\n }\n}"}, "32d1153425241037af4020121bd9e7443b7dc109": {"t": 1785760087.9762654, "n": 15327, "h": "c519bf0f82272a84", "c": "package com.examensarbete;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Properties;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\npublic class AIClient {\n private static final ObjectMapper JSON_MAPPER = new ObjectMapper();\n private static final Properties API_KEYS = loadApiKeys();\n \n // Enhanced regex with flexible whitespace handling\n private static final Pattern BUG_BLOCK_PATTERN = Pattern.compile(\n \"BUG\\\\s+LOCATION:\\\\s*(.+?)\\\\n\" +\n \"BUG\\\\s+TYPE:\\\\s*(.+?)\\\\n\" +\n \"EXPLANATION:\\\\s*([\\\\s\\\\S]*?)\\\\n\" +\n \"ORIGINAL\\\\s+CODE:\\\\s*```java\\\\s*([\\\\s\\\\S]*?)```\\\\s*\\\\n\" +\n \"CORRECTED\\\\s+CODE:\\\\s*```java\\\\s*([\\\\s\\\\S]*?)```\",\n Pattern.CASE_INSENSITIVE | Pattern.DOTALL\n );\n\n private static Properties loadApiKeys() {\n Properties properties = new Properties();\n try (InputStream in = AIClient.class.getClassLoader().getResourceAsStream(\"api-keys.properties\")) {\n if (in != null) {\n properties.load(in);\n }\n } catch (IOException e) {\n // Silent fail - will try environment variables\n }\n return properties;\n }\n\n public static String sendRequest(String model, String prompt, boolean verbose) throws Exception {\n if (verbose) {\n System.out.println(\"\\n=== SENDING REQUEST ===\");\n System.out.println(\"[DEBUG] Model: \" + model);\n System.out.println(\"[DEBUG] Prompt:\\n\" + prompt);\n }\n\n String apiKey = getApiKey(model);\n String apiUrl = getApiUrl(model);\n\n HttpClient client = HttpClient.newHttpClient();\n String requestBody = buildRequestBody(model, prompt);\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(apiUrl))\n .header(\"Content-Type\", \"application/json\")\n .header(model.toLowerCase().equals(\"claude\") ? \"x-api-key\" : \"Authorization\", \n model.toLowerCase().equals(\"claude\") ? apiKey : \"Bearer \" + apiKey)\n .header(\"anthropic-version\", \"2023-06-01\")\n .POST(HttpRequest.BodyPublishers.ofString(requestBody))\n .build();\n\n HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());\n \n if (verbose) {\n System.out.println(\"\\n=== RAW RESPONSE ===\");\n System.out.println(\"[DEBUG] Status Code: \" + response.statusCode());\n System.out.println(\"[DEBUG] Response Body:\\n\" + response.body());\n }\n \n return response.body();\n }\n\n public static AIResponse parseResponse(String response, boolean verbose) throws Exception {\n if (verbose) {\n System.out.println(\"\\n=== PARSING RESPONSE ===\");\n }\n \n String content = extractContentFromApiResponse(response, verbose);\n \n if (content == null || content.trim().isEmpty()) {\n System.err.println(\"[ERROR] Empty or null content in response\");\n return null;\n }\n\n if (verbose) {\n System.out.println(\"[DEBUG] Extracted Content:\\n\" + content);\n }\n \n List<AIResponse.BugFix> bugFixes = new ArrayList<>();\n Matcher matcher = BUG_BLOCK_PATTERN.matcher(content);\n int blockCount = 0;\n \n while (matcher.find()) {\n blockCount++;\n try {\n String bugPosition = matcher.group(1).trim();\n String bugType = matcher.group(2).trim();\n String explanation = matcher.group(3).trim();\n String originalCode = matcher.group(4).trim();\n String correctedCode = matcher.group(5).trim();\n\n // Basic validation\n if (correctedCode.isEmpty() || originalCode.isEmpty()) {\n if (verbose) {\n System.err.println(\"[WARN] Skipping block \" + blockCount + \": Empty code snippet detected\");\n }\n continue;\n }\n\n AIResponse.BugFix bugFix = new AIResponse.BugFix(\n bugPosition, \n correctedCode,\n bugType,\n explanation,\n originalCode\n );\n bugFixes.add(bugFix);\n \n if (verbose) {\n System.out.println(\"[DEBUG] Parsed Bug Fix \" + blockCount + \":\");\n System.out.println(\"- Position: \" + bugPosition);\n System.out.println(\"- Type: \" + bugType);\n System.out.println(\"- Explanation: \" + explanation);\n }\n \n } catch (Exception e) {\n System.err.println(\"[ERROR] Failed to parse bug block \" + blockCount + \": \" + e.getMessage());\n }\n }\n\n // Extract complete file\n String completeFile = null;\n Pattern completeFilePattern = Pattern.compile(\n \"COMPLETE\\\\s+FILE:\\\\s*```java\\\\s*([\\\\s\\\\S]*?)```\",\n Pattern.CASE_INSENSITIVE | Pattern.DOTALL\n );\n Matcher completeFileMatcher = completeFilePattern.matcher(content);\n if (completeFileMatcher.find()) {\n completeFile = completeFileMatcher.group(1).trim();\n }\n\n if (!bugFixes.isEmpty()) {\n if (verbose) {\n System.out.println(\"[INFO] Successfully parsed \" + bugFixes.size() + \" bug fixes out of \" + blockCount + \" blocks\");\n }\n return new AIResponse(bugFixes, completeFile);\n }\n \n if (verbose) {\n System.out.println(\"[ERROR] Failed to parse any bug fixes from \" + blockCount + \" potential blocks\");\n System.out.println(\"[DEBUG] Full content for analysis:\\n\" + content);\n }\n return null;\n }\n\n private static String extractContentFromApiResponse(String response, boolean verbose) throws Exception {\n if (verbose) {\n System.out.println(\"\\n=== EXTRACTING CONTENT ===\");\n }\n \n try {\n JsonNode root = JSON_MAPPER.readTree(response);\n if (verbose) {\n System.out.println(\"[DEBUG] JSON Structure:\\n\" + root.toPrettyString());\n }\n\n if (root.has(\"choices\")) {\n JsonNode choice = root.get(\"choices\").get(0);\n if (choice.has(\"message\") && choice.get(\"message\").has(\"content\")) {\n String content = choice.get(\"message\").get(\"content\").asText();\n if (verbose) {\n System.out.println(\"[DEBUG] Extracted OpenAI content\");\n }\n return content;\n }\n }\n\n if (root.has(\"content\")) {\n StringBuilder contentBuilder = new StringBuilder();\n for (JsonNode item : root.get(\"content\")) {\n if (item.has(\"text\")) {\n contentBuilder.append(item.get(\"text\").asText());\n }\n }\n if (verbose) {\n System.out.println(\"[DEBUG] Extracted Anthropic content\");\n }\n return contentBuilder.toString();\n }\n\n if (root.has(\"response\")) {\n if (verbose) {\n System.out.println(\"[DEBUG] Extracted DeepSeek content\");\n }\n return root.get(\"response\").asText();\n }\n\n if (verbose) {\n System.out.println(\"[WARN] Unknown response format, returning raw response\");\n }\n return response;\n \n } catch (Exception e) {\n System.err.println(\"[ERROR] Failed to parse JSON: \" + e.getMessage());\n if (verbose) {\n System.out.println(\"[DEBUG] Raw response:\\n\" + response);\n }\n return response;\n }\n }\n\n private static String getApiKey(String model) {\n String keyName = model.toUpperCase() + \"_API_KEY\";\n String apiKey = API_KEYS.getProperty(keyName, System.getenv(keyName));\n \n if (apiKey == null || apiKey.isBlank()) {\n throw new RuntimeException(\"Missing API key for \" + model);\n }\n return apiKey;\n }\n\n private static String getApiUrl(String model) {\n return switch (model.toLowerCase()) {\n case \"openai\" -> \"https://api.openai.com/v1/chat/completions\";\n case \"claude\" -> \"https://api.anthropic.com/v1/messages\";\n case \"deepseek\" -> \"https://api.deepseek.com/v1/chat/completions\";\n default -> throw new IllegalArgumentException(\"Unsupported model: \" + model);\n };\n }\n\n private static String buildRequestBody(String model, String prompt) throws Exception {\n return switch (model.toLowerCase()) {\n case \"openai\" -> JSON_MAPPER.writeValueAsString(new OpenAIRequest(prompt));\n case \"claude\" -> JSON_MAPPER.writeValueAsString(new ClaudeRequest(prompt));\n case \"deepseek\" -> JSON_MAPPER.writeValueAsString(new DeepSeekRequest(prompt));\n default -> throw new IllegalArgumentException(\"Unsupported model: \" + model);\n };\n }\n\n public static class AIResponse {\n private final List<BugFix> bugFixes;\n private final String completeFile;\n \n public AIResponse(List<BugFix> bugFixes, String completeFile) {\n this.bugFixes = bugFixes;\n this.completeFile = completeFile;\n }\n\n public List<BugFix> getBugFixes() { \n return bugFixes; \n }\n\n public String getCompleteFile() {\n return completeFile;\n }\n\n public void printBugSummary() {\n // First, collect all bug locations\n StringBuilder locations = new StringBuilder(\"Bug locations: \");\n for (int i = 0; i < bugFixes.size(); i++) {\n BugFix bugFix = bugFixes.get(i);\n String[] positionParts = bugFix.getBugPosition().split(\":\");\n if (positionParts.length >= 2) {\n String lineNumbers = positionParts[1].trim();\n // Remove any file name that might have been included in the line numbers\n lineNumbers = lineNumbers.replaceAll(\"\\\\s*,\\\\s*[^0-9-]+\", \"\");\n locations.append(lineNumbers);\n if (i < bugFixes.size() - 1) {\n locations.append(\", \");\n }\n }\n }\n System.out.println(\"\\n\" + locations.toString());\n \n // Then show each bug's details\n System.out.println(\"\\nBug Details:\");\n for (BugFix bugFix : bugFixes) {\n String[] positionParts = bugFix.getBugPosition().split(\":\");\n if (positionParts.length >= 2) {\n String fileName = positionParts[0].trim();\n String lineNumbers = positionParts[1].trim();\n // Clean up the line numbers\n lineNumbers = lineNumbers.replaceAll(\"\\\\s*,\\\\s*[^0-9-]+\", \"\");\n System.out.println(\"Location: \" + fileName + \":\" + lineNumbers);\n System.out.println(\"Type: \" + bugFix.getBugType());\n System.out.println(\"Explanation: \" + bugFix.getExplanation());\n System.out.println();\n }\n }\n \n // Show the proposed changes\n System.out.println(\"Proposed Changes:\");\n String fileName = bugFixes.get(0).getBugPosition().split(\":\")[0].trim();\n System.out.println(\"file: \" + fileName);\n System.out.println(\"<<<<<<< SEARCH\");\n for (BugFix bugFix : bugFixes) {\n System.out.println(bugFix.getOriginalCode());\n }\n System.out.println(\"=======\");\n for (BugFix bugFix : bugFixes) {\n System.out.println(bugFix.getCorrectedCode());\n }\n System.out.println(\">>>>>>> REPLACE\");\n }\n\n public static class BugFix {\n private final String bugPosition;\n private final String correctedCode;\n private final String bugType;\n private final String explanation;\n private final String originalCode;\n \n public BugFix(String bugPosition, String correctedCode, \n String bugType, String explanation, String originalCode) {\n this.bugPosition = bugPosition;\n this.correctedCode = correctedCode;\n this.bugType = bugType;\n this.explanation = explanation;\n this.originalCode = originalCode;\n }\n\n public String getBugPosition() { return bugPosition; }\n public String getCorrectedCode() { return correctedCode; }\n public String getBugType() { return bugType; }\n public String getExplanation() { return explanation; }\n public String getOriginalCode() { return originalCode; }\n }\n }\n\n private static class OpenAIRequest {\n public String model = \"gpt-4.1\";\n public List<Message> messages;\n\n public OpenAIRequest(String prompt) {\n this.messages = List.of(new Message(\"user\", prompt));\n }\n\n static class Message {\n public String role;\n public String content;\n public Message(String role, String content) {\n this.role = role;\n this.content = content;\n }\n }\n }\n\n private static class ClaudeRequest {\n public String model = \"claude-3-7-sonnet-20250219\";\n public int max_tokens = 8192;\n public List<Message> messages;\n\n public ClaudeRequest(String prompt) {\n this.messages = List.of(new Message(prompt));\n }\n\n static class Message {\n public String role = \"user\";\n public List<Content> content;\n public Message(String prompt) {\n this.content = List.of(new Content(prompt));\n }\n static class Content {\n public String type = \"text\";\n public String text;\n public Content(String text) {\n this.text = text;\n }\n }\n }\n }\n\n private static class DeepSeekRequest {\n public String model = \"deepseek-reasoner\";\n public int max_tokens = 8192;\n public List<Message> messages;\n\n public DeepSeekRequest(String prompt) {\n this.messages = List.of(new Message(\"user\", prompt));\n }\n\n static class Message {\n public String role;\n public String content;\n public Message(String role, String content) {\n this.role = role;\n this.content = content;\n }\n }\n }\n}"}, "81c85ef42a699f1c828f769b96b57f7564a9070b": {"t": 1785760089.1906552, "n": 15322, "h": "5a5279cdbf777e34", "c": "package com.examensarbete;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Properties;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\npublic class AIClient {\n private static final ObjectMapper JSON_MAPPER = new ObjectMapper();\n private static final Properties API_KEYS = loadApiKeys();\n \n // Enhanced regex with flexible whitespace handling\n private static final Pattern BUG_BLOCK_PATTERN = Pattern.compile(\n \"BUG\\\\s+LOCATION:\\\\s*(.+?)\\\\n\" +\n \"BUG\\\\s+TYPE:\\\\s*(.+?)\\\\n\" +\n \"EXPLANATION:\\\\s*([\\\\s\\\\S]*?)\\\\n\" +\n \"ORIGINAL\\\\s+CODE:\\\\s*```java\\\\s*([\\\\s\\\\S]*?)```\\\\s*\\\\n\" +\n \"CORRECTED\\\\s+CODE:\\\\s*```java\\\\s*([\\\\s\\\\S]*?)```\",\n Pattern.CASE_INSENSITIVE | Pattern.DOTALL\n );\n\n private static Properties loadApiKeys() {\n Properties properties = new Properties();\n try (InputStream in = AIClient.class.getClassLoader().getResourceAsStream(\"api-keys.properties\")) {\n if (in != null) {\n properties.load(in);\n }\n } catch (IOException e) {\n // Silent fail - will try environment variables\n }\n return properties;\n }\n\n public static String sendRequest(String model, String prompt, boolean verbose) throws Exception {\n if (verbose) {\n System.out.println(\"\\n=== SENDING REQUEST ===\");\n System.out.println(\"[DEBUG] Model: \" + model);\n System.out.println(\"[DEBUG] Prompt:\\n\" + prompt);\n }\n\n String apiKey = getApiKey(model);\n String apiUrl = getApiUrl(model);\n\n HttpClient client = HttpClient.newHttpClient();\n String requestBody = buildRequestBody(model, prompt);\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(apiUrl))\n .header(\"Content-Type\", \"application/json\")\n .header(model.toLowerCase().equals(\"claude\") ? \"x-api-key\" : \"Authorization\", \n model.toLowerCase().equals(\"claude\") ? apiKey : \"Bearer \" + apiKey)\n .header(\"anthropic-version\", \"2023-06-01\")\n .POST(HttpRequest.BodyPublishers.ofString(requestBody))\n .build();\n\n HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());\n \n if (verbose) {\n System.out.println(\"\\n=== RAW RESPONSE ===\");\n System.out.println(\"[DEBUG] Status Code: \" + response.statusCode());\n System.out.println(\"[DEBUG] Response Body:\\n\" + response.body());\n }\n \n return response.body();\n }\n\n public static AIResponse parseResponse(String response, boolean verbose) throws Exception {\n if (verbose) {\n System.out.println(\"\\n=== PARSING RESPONSE ===\");\n }\n \n String content = extractContentFromApiResponse(response, verbose);\n \n if (content == null || content.trim().isEmpty()) {\n System.err.println(\"[ERROR] Empty or null content in response\");\n return null;\n }\n\n if (verbose) {\n System.out.println(\"[DEBUG] Extracted Content:\\n\" + content);\n }\n \n List<AIResponse.BugFix> bugFixes = new ArrayList<>();\n Matcher matcher = BUG_BLOCK_PATTERN.matcher(content);\n int blockCount = 0;\n \n while (matcher.find()) {\n blockCount++;\n try {\n String bugPosition = matcher.group(1).trim();\n String bugType = matcher.group(2).trim();\n String explanation = matcher.group(3).trim();\n String originalCode = matcher.group(4).trim();\n String correctedCode = matcher.group(5).trim();\n\n // Basic validation\n if (correctedCode.isEmpty() || originalCode.isEmpty()) {\n if (verbose) {\n System.err.println(\"[WARN] Skipping block \" + blockCount + \": Empty code snippet detected\");\n }\n continue;\n }\n\n AIResponse.BugFix bugFix = new AIResponse.BugFix(\n bugPosition, \n correctedCode,\n bugType,\n explanation,\n originalCode\n );\n bugFixes.add(bugFix);\n \n if (verbose) {\n System.out.println(\"[DEBUG] Parsed Bug Fix \" + blockCount + \":\");\n System.out.println(\"- Position: \" + bugPosition);\n System.out.println(\"- Type: \" + bugType);\n System.out.println(\"- Explanation: \" + explanation);\n }\n \n } catch (Exception e) {\n System.err.println(\"[ERROR] Failed to parse bug block \" + blockCount + \": \" + e.getMessage());\n }\n }\n\n // Extract complete file\n String completeFile = null;\n Pattern completeFilePattern = Pattern.compile(\n \"COMPLETE\\\\s+FILE:\\\\s*```java\\\\s*([\\\\s\\\\S]*?)```\",\n Pattern.CASE_INSENSITIVE | Pattern.DOTALL\n );\n Matcher completeFileMatcher = completeFilePattern.matcher(content);\n if (completeFileMatcher.find()) {\n completeFile = completeFileMatcher.group(1).trim();\n }\n\n if (!bugFixes.isEmpty()) {\n if (verbose) {\n System.out.println(\"[INFO] Successfully parsed \" + bugFixes.size() + \" bug fixes out of \" + blockCount + \" blocks\");\n }\n return new AIResponse(bugFixes, completeFile);\n }\n \n if (verbose) {\n System.out.println(\"[ERROR] Failed to parse any bug fixes from \" + blockCount + \" potential blocks\");\n System.out.println(\"[DEBUG] Full content for analysis:\\n\" + content);\n }\n return null;\n }\n\n private static String extractContentFromApiResponse(String response, boolean verbose) throws Exception {\n if (verbose) {\n System.out.println(\"\\n=== EXTRACTING CONTENT ===\");\n }\n \n try {\n JsonNode root = JSON_MAPPER.readTree(response);\n if (verbose) {\n System.out.println(\"[DEBUG] JSON Structure:\\n\" + root.toPrettyString());\n }\n\n if (root.has(\"choices\")) {\n JsonNode choice = root.get(\"choices\").get(0);\n if (choice.has(\"message\") && choice.get(\"message\").has(\"content\")) {\n String content = choice.get(\"message\").get(\"content\").asText();\n if (verbose) {\n System.out.println(\"[DEBUG] Extracted OpenAI content\");\n }\n return content;\n }\n }\n\n if (root.has(\"content\")) {\n StringBuilder contentBuilder = new StringBuilder();\n for (JsonNode item : root.get(\"content\")) {\n if (item.has(\"text\")) {\n contentBuilder.append(item.get(\"text\").asText());\n }\n }\n if (verbose) {\n System.out.println(\"[DEBUG] Extracted Anthropic content\");\n }\n return contentBuilder.toString();\n }\n\n if (root.has(\"response\")) {\n if (verbose) {\n System.out.println(\"[DEBUG] Extracted DeepSeek content\");\n }\n return root.get(\"response\").asText();\n }\n\n if (verbose) {\n System.out.println(\"[WARN] Unknown response format, returning raw response\");\n }\n return response;\n \n } catch (Exception e) {\n System.err.println(\"[ERROR] Failed to parse JSON: \" + e.getMessage());\n if (verbose) {\n System.out.println(\"[DEBUG] Raw response:\\n\" + response);\n }\n return response;\n }\n }\n\n private static String getApiKey(String model) {\n String keyName = model.toUpperCase() + \"_API_KEY\";\n String apiKey = API_KEYS.getProperty(keyName, System.getenv(keyName));\n \n if (apiKey == null || apiKey.isBlank()) {\n throw new RuntimeException(\"Missing API key for \" + model);\n }\n return apiKey;\n }\n\n private static String getApiUrl(String model) {\n return switch (model.toLowerCase()) {\n case \"openai\" -> \"https://api.openai.com/v1/chat/completions\";\n case \"claude\" -> \"https://api.anthropic.com/v1/messages\";\n case \"deepseek\" -> \"https://api.deepseek.com/v1/chat/completions\";\n default -> throw new IllegalArgumentException(\"Unsupported model: \" + model);\n };\n }\n\n private static String buildRequestBody(String model, String prompt) throws Exception {\n return switch (model.toLowerCase()) {\n case \"openai\" -> JSON_MAPPER.writeValueAsString(new OpenAIRequest(prompt));\n case \"claude\" -> JSON_MAPPER.writeValueAsString(new ClaudeRequest(prompt));\n case \"deepseek\" -> JSON_MAPPER.writeValueAsString(new DeepSeekRequest(prompt));\n default -> throw new IllegalArgumentException(\"Unsupported model: \" + model);\n };\n }\n\n public static class AIResponse {\n private final List<BugFix> bugFixes;\n private final String completeFile;\n \n public AIResponse(List<BugFix> bugFixes, String completeFile) {\n this.bugFixes = bugFixes;\n this.completeFile = completeFile;\n }\n\n public List<BugFix> getBugFixes() { \n return bugFixes; \n }\n\n public String getCompleteFile() {\n return completeFile;\n }\n\n public void printBugSummary() {\n // First, collect all bug locations\n StringBuilder locations = new StringBuilder(\"Bug locations: \");\n for (int i = 0; i < bugFixes.size(); i++) {\n BugFix bugFix = bugFixes.get(i);\n String[] positionParts = bugFix.getBugPosition().split(\":\");\n if (positionParts.length >= 2) {\n String lineNumbers = positionParts[1].trim();\n // Remove any file name that might have been included in the line numbers\n lineNumbers = lineNumbers.replaceAll(\"\\\\s*,\\\\s*[^0-9-]+\", \"\");\n locations.append(lineNumbers);\n if (i < bugFixes.size() - 1) {\n locations.append(\", \");\n }\n }\n }\n System.out.println(\"\\n\" + locations.toString());\n \n // Then show each bug's details\n System.out.println(\"\\nBug Details:\");\n for (BugFix bugFix : bugFixes) {\n String[] positionParts = bugFix.getBugPosition().split(\":\");\n if (positionParts.length >= 2) {\n String fileName = positionParts[0].trim();\n String lineNumbers = positionParts[1].trim();\n // Clean up the line numbers\n lineNumbers = lineNumbers.replaceAll(\"\\\\s*,\\\\s*[^0-9-]+\", \"\");\n System.out.println(\"Location: \" + fileName + \":\" + lineNumbers);\n System.out.println(\"Type: \" + bugFix.getBugType());\n System.out.println(\"Explanation: \" + bugFix.getExplanation());\n System.out.println();\n }\n }\n \n // Show the proposed changes\n System.out.println(\"Proposed Changes:\");\n String fileName = bugFixes.get(0).getBugPosition().split(\":\")[0].trim();\n System.out.println(\"file: \" + fileName);\n System.out.println(\"<<<<<<< SEARCH\");\n for (BugFix bugFix : bugFixes) {\n System.out.println(bugFix.getOriginalCode());\n }\n System.out.println(\"=======\");\n for (BugFix bugFix : bugFixes) {\n System.out.println(bugFix.getCorrectedCode());\n }\n System.out.println(\">>>>>>> REPLACE\");\n }\n\n public static class BugFix {\n private final String bugPosition;\n private final String correctedCode;\n private final String bugType;\n private final String explanation;\n private final String originalCode;\n \n public BugFix(String bugPosition, String correctedCode, \n String bugType, String explanation, String originalCode) {\n this.bugPosition = bugPosition;\n this.correctedCode = correctedCode;\n this.bugType = bugType;\n this.explanation = explanation;\n this.originalCode = originalCode;\n }\n\n public String getBugPosition() { return bugPosition; }\n public String getCorrectedCode() { return correctedCode; }\n public String getBugType() { return bugType; }\n public String getExplanation() { return explanation; }\n public String getOriginalCode() { return originalCode; }\n }\n }\n\n private static class OpenAIRequest {\n public String model = \"gpt-4.1\";\n public List<Message> messages;\n\n public OpenAIRequest(String prompt) {\n this.messages = List.of(new Message(\"user\", prompt));\n }\n\n static class Message {\n public String role;\n public String content;\n public Message(String role, String content) {\n this.role = role;\n this.content = content;\n }\n }\n }\n\n private static class ClaudeRequest {\n public String model = \"claude-3-7-sonnet-20250219\";\n public int max_tokens = 4000;\n public List<Message> messages;\n\n public ClaudeRequest(String prompt) {\n this.messages = List.of(new Message(prompt));\n }\n\n static class Message {\n public String role = \"user\";\n public List<Content> content;\n public Message(String prompt) {\n this.content = List.of(new Content(prompt));\n }\n static class Content {\n public String type = \"text\";\n public String text;\n public Content(String text) {\n this.text = text;\n }\n }\n }\n }\n\n private static class DeepSeekRequest {\n public String model = \"deepseek-r1\";\n public int max_tokens = 16000;\n public List<Message> messages;\n\n public DeepSeekRequest(String prompt) {\n this.messages = List.of(new Message(\"user\", prompt));\n }\n\n static class Message {\n public String role;\n public String content;\n public Message(String role, String content) {\n this.role = role;\n this.content = content;\n }\n }\n }\n}"}, "f0ff0da224f59d352d643fa1d06c4adb16316d2b": {"t": 1785760090.5887458, "n": 15284, "h": "a905dc8bc139665c", "c": "package com.examensarbete;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Properties;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\npublic class AIClient {\n private static final ObjectMapper JSON_MAPPER = new ObjectMapper();\n private static final Properties API_KEYS = loadApiKeys();\n \n // Enhanced regex with flexible whitespace handling\n private static final Pattern BUG_BLOCK_PATTERN = Pattern.compile(\n \"BUG\\\\s+LOCATION:\\\\s*(.+?)\\\\n\" +\n \"BUG\\\\s+TYPE:\\\\s*(.+?)\\\\n\" +\n \"EXPLANATION:\\\\s*([\\\\s\\\\S]*?)\\\\n\" +\n \"ORIGINAL\\\\s+CODE:\\\\s*```java\\\\s*([\\\\s\\\\S]*?)```\\\\s*\\\\n\" +\n \"CORRECTED\\\\s+CODE:\\\\s*```java\\\\s*([\\\\s\\\\S]*?)```\",\n Pattern.CASE_INSENSITIVE | Pattern.DOTALL\n );\n\n private static Properties loadApiKeys() {\n Properties properties = new Properties();\n try (InputStream in = AIClient.class.getClassLoader().getResourceAsStream(\"api-keys.properties\")) {\n if (in != null) {\n properties.load(in);\n }\n } catch (IOException e) {\n // Silent fail - will try environment variables\n }\n return properties;\n }\n\n public static String sendRequest(String model, String prompt, boolean verbose) throws Exception {\n if (verbose) {\n System.out.println(\"\\n=== SENDING REQUEST ===\");\n System.out.println(\"[DEBUG] Model: \" + model);\n System.out.println(\"[DEBUG] Prompt:\\n\" + prompt);\n }\n\n String apiKey = getApiKey(model);\n String apiUrl = getApiUrl(model);\n\n HttpClient client = HttpClient.newHttpClient();\n String requestBody = buildRequestBody(model, prompt);\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(apiUrl))\n .header(\"Content-Type\", \"application/json\")\n .header(model.toLowerCase().equals(\"claude\") ? \"x-api-key\" : \"Authorization\", \n model.toLowerCase().equals(\"claude\") ? apiKey : \"Bearer \" + apiKey)\n .header(\"anthropic-version\", \"2023-06-01\")\n .POST(HttpRequest.BodyPublishers.ofString(requestBody))\n .build();\n\n HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());\n \n if (verbose) {\n System.out.println(\"\\n=== RAW RESPONSE ===\");\n System.out.println(\"[DEBUG] Status Code: \" + response.statusCode());\n System.out.println(\"[DEBUG] Response Body:\\n\" + response.body());\n }\n \n return response.body();\n }\n\n public static AIResponse parseResponse(String response, boolean verbose) throws Exception {\n if (verbose) {\n System.out.println(\"\\n=== PARSING RESPONSE ===\");\n }\n \n String content = extractContentFromApiResponse(response, verbose);\n \n if (content == null || content.trim().isEmpty()) {\n System.err.println(\"[ERROR] Empty or null content in response\");\n return null;\n }\n\n if (verbose) {\n System.out.println(\"[DEBUG] Extracted Content:\\n\" + content);\n }\n \n List<AIResponse.BugFix> bugFixes = new ArrayList<>();\n Matcher matcher = BUG_BLOCK_PATTERN.matcher(content);\n int blockCount = 0;\n \n while (matcher.find()) {\n blockCount++;\n try {\n String bugPosition = matcher.group(1).trim();\n String bugType = matcher.group(2).trim();\n String explanation = matcher.group(3).trim();\n String originalCode = matcher.group(4).trim();\n String correctedCode = matcher.group(5).trim();\n\n // Basic validation\n if (correctedCode.isEmpty() || originalCode.isEmpty()) {\n if (verbose) {\n System.err.println(\"[WARN] Skipping block \" + blockCount + \": Empty code snippet detected\");\n }\n continue;\n }\n\n AIResponse.BugFix bugFix = new AIResponse.BugFix(\n bugPosition, \n correctedCode,\n bugType,\n explanation,\n originalCode\n );\n bugFixes.add(bugFix);\n \n if (verbose) {\n System.out.println(\"[DEBUG] Parsed Bug Fix \" + blockCount + \":\");\n System.out.println(\"- Position: \" + bugPosition);\n System.out.println(\"- Type: \" + bugType);\n System.out.println(\"- Explanation: \" + explanation);\n }\n \n } catch (Exception e) {\n System.err.println(\"[ERROR] Failed to parse bug block \" + blockCount + \": \" + e.getMessage());\n }\n }\n\n // Extract complete file\n String completeFile = null;\n Pattern completeFilePattern = Pattern.compile(\n \"COMPLETE\\\\s+FILE:\\\\s*```java\\\\s*([\\\\s\\\\S]*?)```\",\n Pattern.CASE_INSENSITIVE | Pattern.DOTALL\n );\n Matcher completeFileMatcher = completeFilePattern.matcher(content);\n if (completeFileMatcher.find()) {\n completeFile = completeFileMatcher.group(1).trim();\n }\n\n if (!bugFixes.isEmpty()) {\n if (verbose) {\n System.out.println(\"[INFO] Successfully parsed \" + bugFixes.size() + \" bug fixes out of \" + blockCount + \" blocks\");\n }\n return new AIResponse(bugFixes, completeFile);\n }\n \n if (verbose) {\n System.out.println(\"[ERROR] Failed to parse any bug fixes from \" + blockCount + \" potential blocks\");\n System.out.println(\"[DEBUG] Full content for analysis:\\n\" + content);\n }\n return null;\n }\n\n private static String extractContentFromApiResponse(String response, boolean verbose) throws Exception {\n if (verbose) {\n System.out.println(\"\\n=== EXTRACTING CONTENT ===\");\n }\n \n try {\n JsonNode root = JSON_MAPPER.readTree(response);\n if (verbose) {\n System.out.println(\"[DEBUG] JSON Structure:\\n\" + root.toPrettyString());\n }\n\n if (root.has(\"choices\")) {\n JsonNode choice = root.get(\"choices\").get(0);\n if (choice.has(\"message\") && choice.get(\"message\").has(\"content\")) {\n String content = choice.get(\"message\").get(\"content\").asText();\n if (verbose) {\n System.out.println(\"[DEBUG] Extracted OpenAI content\");\n }\n return content;\n }\n }\n\n if (root.has(\"content\")) {\n StringBuilder contentBuilder = new StringBuilder();\n for (JsonNode item : root.get(\"content\")) {\n if (item.has(\"text\")) {\n contentBuilder.append(item.get(\"text\").asText());\n }\n }\n if (verbose) {\n System.out.println(\"[DEBUG] Extracted Anthropic content\");\n }\n return contentBuilder.toString();\n }\n\n if (root.has(\"response\")) {\n if (verbose) {\n System.out.println(\"[DEBUG] Extracted DeepSeek content\");\n }\n return root.get(\"response\").asText();\n }\n\n if (verbose) {\n System.out.println(\"[WARN] Unknown response format, returning raw response\");\n }\n return response;\n \n } catch (Exception e) {\n System.err.println(\"[ERROR] Failed to parse JSON: \" + e.getMessage());\n if (verbose) {\n System.out.println(\"[DEBUG] Raw response:\\n\" + response);\n }\n return response;\n }\n }\n\n private static String getApiKey(String model) {\n String keyName = model.toUpperCase() + \"_API_KEY\";\n String apiKey = API_KEYS.getProperty(keyName, System.getenv(keyName));\n \n if (apiKey == null || apiKey.isBlank()) {\n throw new RuntimeException(\"Missing API key for \" + model);\n }\n return apiKey;\n }\n\n private static String getApiUrl(String model) {\n return switch (model.toLowerCase()) {\n case \"openai\" -> \"https://api.openai.com/v1/chat/completions\";\n case \"claude\" -> \"https://api.anthropic.com/v1/messages\";\n case \"deepseek\" -> \"https://api.deepseek.com/v1/chat/completions\";\n default -> throw new IllegalArgumentException(\"Unsupported model: \" + model);\n };\n }\n\n private static String buildRequestBody(String model, String prompt) throws Exception {\n return switch (model.toLowerCase()) {\n case \"openai\" -> JSON_MAPPER.writeValueAsString(new OpenAIRequest(prompt));\n case \"claude\" -> JSON_MAPPER.writeValueAsString(new ClaudeRequest(prompt));\n case \"deepseek\" -> JSON_MAPPER.writeValueAsString(new DeepSeekRequest(prompt));\n default -> throw new IllegalArgumentException(\"Unsupported model: \" + model);\n };\n }\n\n public static class AIResponse {\n private final List<BugFix> bugFixes;\n private final String completeFile;\n \n public AIResponse(List<BugFix> bugFixes, String completeFile) {\n this.bugFixes = bugFixes;\n this.completeFile = completeFile;\n }\n\n public List<BugFix> getBugFixes() { \n return bugFixes; \n }\n\n public String getCompleteFile() {\n return completeFile;\n }\n\n public void printBugSummary() {\n // First, collect all bug locations\n StringBuilder locations = new StringBuilder(\"Bug locations: \");\n for (int i = 0; i < bugFixes.size(); i++) {\n BugFix bugFix = bugFixes.get(i);\n String[] positionParts = bugFix.getBugPosition().split(\":\");\n if (positionParts.length >= 2) {\n String lineNumbers = positionParts[1].trim();\n // Remove any file name that might have been included in the line numbers\n lineNumbers = lineNumbers.replaceAll(\"\\\\s*,\\\\s*[^0-9-]+\", \"\");\n locations.append(lineNumbers);\n if (i < bugFixes.size() - 1) {\n locations.append(\", \");\n }\n }\n }\n System.out.println(\"\\n\" + locations.toString());\n \n // Then show each bug's details\n System.out.println(\"\\nBug Details:\");\n for (BugFix bugFix : bugFixes) {\n String[] positionParts = bugFix.getBugPosition().split(\":\");\n if (positionParts.length >= 2) {\n String fileName = positionParts[0].trim();\n String lineNumbers = positionParts[1].trim();\n // Clean up the line numbers\n lineNumbers = lineNumbers.replaceAll(\"\\\\s*,\\\\s*[^0-9-]+\", \"\");\n System.out.println(\"Location: \" + fileName + \":\" + lineNumbers);\n System.out.println(\"Type: \" + bugFix.getBugType());\n System.out.println(\"Explanation: \" + bugFix.getExplanation());\n System.out.println();\n }\n }\n \n // Show the proposed changes\n System.out.println(\"Proposed Changes:\");\n String fileName = bugFixes.get(0).getBugPosition().split(\":\")[0].trim();\n System.out.println(\"file: \" + fileName);\n System.out.println(\"<<<<<<< SEARCH\");\n for (BugFix bugFix : bugFixes) {\n System.out.println(bugFix.getOriginalCode());\n }\n System.out.println(\"=======\");\n for (BugFix bugFix : bugFixes) {\n System.out.println(bugFix.getCorrectedCode());\n }\n System.out.println(\">>>>>>> REPLACE\");\n }\n\n public static class BugFix {\n private final String bugPosition;\n private final String correctedCode;\n private final String bugType;\n private final String explanation;\n private final String originalCode;\n \n public BugFix(String bugPosition, String correctedCode, \n String bugType, String explanation, String originalCode) {\n this.bugPosition = bugPosition;\n this.correctedCode = correctedCode;\n this.bugType = bugType;\n this.explanation = explanation;\n this.originalCode = originalCode;\n }\n\n public String getBugPosition() { return bugPosition; }\n public String getCorrectedCode() { return correctedCode; }\n public String getBugType() { return bugType; }\n public String getExplanation() { return explanation; }\n public String getOriginalCode() { return originalCode; }\n }\n }\n\n private static class OpenAIRequest {\n public String model = \"gpt-4\";\n public List<Message> messages;\n\n public OpenAIRequest(String prompt) {\n this.messages = List.of(new Message(\"user\", prompt));\n }\n\n static class Message {\n public String role;\n public String content;\n public Message(String role, String content) {\n this.role = role;\n this.content = content;\n }\n }\n }\n\n private static class ClaudeRequest {\n public String model = \"claude-3-7-sonnet-20250219\";\n public int max_tokens = 4000;\n public List<Message> messages;\n\n public ClaudeRequest(String prompt) {\n this.messages = List.of(new Message(prompt));\n }\n\n static class Message {\n public String role = \"user\";\n public List<Content> content;\n public Message(String prompt) {\n this.content = List.of(new Content(prompt));\n }\n static class Content {\n public String type = \"text\";\n public String text;\n public Content(String text) {\n this.text = text;\n }\n }\n }\n }\n\n private static class DeepSeekRequest {\n public String model = \"deepseek-coder\";\n public List<Message> messages;\n\n public DeepSeekRequest(String prompt) {\n this.messages = List.of(new Message(\"user\", prompt));\n }\n\n static class Message {\n public String role;\n public String content;\n public Message(String role, String content) {\n this.role = role;\n this.content = content;\n }\n }\n }\n}"}, "bde3b0607ee158e5c3b4953e1fae3854e8b27ef4": {"t": 1785760092.0367653, "n": 15096, "h": "ca57f6ac97ef3363", "c": "package com.examensarbete;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Properties;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\npublic class AIClient {\n private static final ObjectMapper JSON_MAPPER = new ObjectMapper();\n private static final Properties API_KEYS = loadApiKeys();\n \n // Enhanced regex with flexible whitespace handling\n private static final Pattern BUG_BLOCK_PATTERN = Pattern.compile(\n \"BUG\\\\s+LOCATION:\\\\s*(.+?)\\\\n\" +\n \"BUG\\\\s+TYPE:\\\\s*(.+?)\\\\n\" +\n \"EXPLANATION:\\\\s*([\\\\s\\\\S]*?)\\\\n\" +\n \"ORIGINAL\\\\s+CODE:\\\\s*```java\\\\s*([\\\\s\\\\S]*?)```\\\\s*\\\\n\" +\n \"CORRECTED\\\\s+CODE:\\\\s*```java\\\\s*([\\\\s\\\\S]*?)```\",\n Pattern.CASE_INSENSITIVE | Pattern.DOTALL\n );\n\n private static Properties loadApiKeys() {\n Properties properties = new Properties();\n try (InputStream in = AIClient.class.getClassLoader().getResourceAsStream(\"api-keys.properties\")) {\n if (in != null) {\n properties.load(in);\n }\n } catch (IOException e) {\n // Silent fail - will try environment variables\n }\n return properties;\n }\n\n public static String sendRequest(String model, String prompt, boolean verbose) throws Exception {\n if (verbose) {\n System.out.println(\"\\n=== SENDING REQUEST ===\");\n System.out.println(\"[DEBUG] Model: \" + model);\n System.out.println(\"[DEBUG] Prompt:\\n\" + prompt);\n }\n\n String apiKey = getApiKey(model);\n String apiUrl = getApiUrl(model);\n\n HttpClient client = HttpClient.newHttpClient();\n String requestBody = buildRequestBody(model, prompt);\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(apiUrl))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + apiKey)\n .POST(HttpRequest.BodyPublishers.ofString(requestBody))\n .build();\n\n HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());\n \n if (verbose) {\n System.out.println(\"\\n=== RAW RESPONSE ===\");\n System.out.println(\"[DEBUG] Status Code: \" + response.statusCode());\n System.out.println(\"[DEBUG] Response Body:\\n\" + response.body());\n }\n \n return response.body();\n }\n\n public static AIResponse parseResponse(String response, boolean verbose) throws Exception {\n if (verbose) {\n System.out.println(\"\\n=== PARSING RESPONSE ===\");\n }\n \n String content = extractContentFromApiResponse(response, verbose);\n \n if (content == null || content.trim().isEmpty()) {\n System.err.println(\"[ERROR] Empty or null content in response\");\n return null;\n }\n\n if (verbose) {\n System.out.println(\"[DEBUG] Extracted Content:\\n\" + content);\n }\n \n List<AIResponse.BugFix> bugFixes = new ArrayList<>();\n Matcher matcher = BUG_BLOCK_PATTERN.matcher(content);\n int blockCount = 0;\n \n while (matcher.find()) {\n blockCount++;\n try {\n String bugPosition = matcher.group(1).trim();\n String bugType = matcher.group(2).trim();\n String explanation = matcher.group(3).trim();\n String originalCode = matcher.group(4).trim();\n String correctedCode = matcher.group(5).trim();\n\n // Basic validation\n if (correctedCode.isEmpty() || originalCode.isEmpty()) {\n if (verbose) {\n System.err.println(\"[WARN] Skipping block \" + blockCount + \": Empty code snippet detected\");\n }\n continue;\n }\n\n AIResponse.BugFix bugFix = new AIResponse.BugFix(\n bugPosition, \n correctedCode,\n bugType,\n explanation,\n originalCode\n );\n bugFixes.add(bugFix);\n \n if (verbose) {\n System.out.println(\"[DEBUG] Parsed Bug Fix \" + blockCount + \":\");\n System.out.println(\"- Position: \" + bugPosition);\n System.out.println(\"- Type: \" + bugType);\n System.out.println(\"- Explanation: \" + explanation);\n }\n \n } catch (Exception e) {\n System.err.println(\"[ERROR] Failed to parse bug block \" + blockCount + \": \" + e.getMessage());\n }\n }\n\n // Extract complete file\n String completeFile = null;\n Pattern completeFilePattern = Pattern.compile(\n \"COMPLETE\\\\s+FILE:\\\\s*```java\\\\s*([\\\\s\\\\S]*?)```\",\n Pattern.CASE_INSENSITIVE | Pattern.DOTALL\n );\n Matcher completeFileMatcher = completeFilePattern.matcher(content);\n if (completeFileMatcher.find()) {\n completeFile = completeFileMatcher.group(1).trim();\n }\n\n if (!bugFixes.isEmpty()) {\n if (verbose) {\n System.out.println(\"[INFO] Successfully parsed \" + bugFixes.size() + \" bug fixes out of \" + blockCount + \" blocks\");\n }\n return new AIResponse(bugFixes, completeFile);\n }\n \n if (verbose) {\n System.out.println(\"[ERROR] Failed to parse any bug fixes from \" + blockCount + \" potential blocks\");\n System.out.println(\"[DEBUG] Full content for analysis:\\n\" + content);\n }\n return null;\n }\n\n private static String extractContentFromApiResponse(String response, boolean verbose) throws Exception {\n if (verbose) {\n System.out.println(\"\\n=== EXTRACTING CONTENT ===\");\n }\n \n try {\n JsonNode root = JSON_MAPPER.readTree(response);\n if (verbose) {\n System.out.println(\"[DEBUG] JSON Structure:\\n\" + root.toPrettyString());\n }\n\n if (root.has(\"choices\")) {\n JsonNode choice = root.get(\"choices\").get(0);\n if (choice.has(\"message\") && choice.get(\"message\").has(\"content\")) {\n String content = choice.get(\"message\").get(\"content\").asText();\n if (verbose) {\n System.out.println(\"[DEBUG] Extracted OpenAI content\");\n }\n return content;\n }\n }\n\n if (root.has(\"content\")) {\n StringBuilder contentBuilder = new StringBuilder();\n for (JsonNode item : root.get(\"content\")) {\n if (item.has(\"text\")) {\n contentBuilder.append(item.get(\"text\").asText());\n }\n }\n if (verbose) {\n System.out.println(\"[DEBUG] Extracted Anthropic content\");\n }\n return contentBuilder.toString();\n }\n\n if (root.has(\"response\")) {\n if (verbose) {\n System.out.println(\"[DEBUG] Extracted DeepSeek content\");\n }\n return root.get(\"response\").asText();\n }\n\n if (verbose) {\n System.out.println(\"[WARN] Unknown response format, returning raw response\");\n }\n return response;\n \n } catch (Exception e) {\n System.err.println(\"[ERROR] Failed to parse JSON: \" + e.getMessage());\n if (verbose) {\n System.out.println(\"[DEBUG] Raw response:\\n\" + response);\n }\n return response;\n }\n }\n\n private static String getApiKey(String model) {\n String keyName = model.toUpperCase() + \"_API_KEY\";\n String apiKey = API_KEYS.getProperty(keyName, System.getenv(keyName));\n \n if (apiKey == null || apiKey.isBlank()) {\n throw new RuntimeException(\"Missing API key for \" + model);\n }\n return apiKey;\n }\n\n private static String getApiUrl(String model) {\n return switch (model.toLowerCase()) {\n case \"openai\" -> \"https://api.openai.com/v1/chat/completions\";\n case \"claude\" -> \"https://api.anthropic.com/v1/messages\";\n case \"deepseek\" -> \"https://api.deepseek.com/v1/chat/completions\";\n default -> throw new IllegalArgumentException(\"Unsupported model: \" + model);\n };\n }\n\n private static String buildRequestBody(String model, String prompt) throws Exception {\n return switch (model.toLowerCase()) {\n case \"openai\" -> JSON_MAPPER.writeValueAsString(new OpenAIRequest(prompt));\n case \"claude\" -> JSON_MAPPER.writeValueAsString(new ClaudeRequest(prompt));\n case \"deepseek\" -> JSON_MAPPER.writeValueAsString(new DeepSeekRequest(prompt));\n default -> throw new IllegalArgumentException(\"Unsupported model: \" + model);\n };\n }\n\n public static class AIResponse {\n private final List<BugFix> bugFixes;\n private final String completeFile;\n \n public AIResponse(List<BugFix> bugFixes, String completeFile) {\n this.bugFixes = bugFixes;\n this.completeFile = completeFile;\n }\n\n public List<BugFix> getBugFixes() { \n return bugFixes; \n }\n\n public String getCompleteFile() {\n return completeFile;\n }\n\n public void printBugSummary() {\n // First, collect all bug locations\n StringBuilder locations = new StringBuilder(\"Bug locations: \");\n for (int i = 0; i < bugFixes.size(); i++) {\n BugFix bugFix = bugFixes.get(i);\n String[] positionParts = bugFix.getBugPosition().split(\":\");\n if (positionParts.length >= 2) {\n String lineNumbers = positionParts[1].trim();\n // Remove any file name that might have been included in the line numbers\n lineNumbers = lineNumbers.replaceAll(\"\\\\s*,\\\\s*[^0-9-]+\", \"\");\n locations.append(lineNumbers);\n if (i < bugFixes.size() - 1) {\n locations.append(\", \");\n }\n }\n }\n System.out.println(\"\\n\" + locations.toString());\n \n // Then show each bug's details\n System.out.println(\"\\nBug Details:\");\n for (BugFix bugFix : bugFixes) {\n String[] positionParts = bugFix.getBugPosition().split(\":\");\n if (positionParts.length >= 2) {\n String fileName = positionParts[0].trim();\n String lineNumbers = positionParts[1].trim();\n // Clean up the line numbers\n lineNumbers = lineNumbers.replaceAll(\"\\\\s*,\\\\s*[^0-9-]+\", \"\");\n System.out.println(\"Location: \" + fileName + \":\" + lineNumbers);\n System.out.println(\"Type: \" + bugFix.getBugType());\n System.out.println(\"Explanation: \" + bugFix.getExplanation());\n System.out.println();\n }\n }\n \n // Show the proposed changes\n System.out.println(\"Proposed Changes:\");\n String fileName = bugFixes.get(0).getBugPosition().split(\":\")[0].trim();\n System.out.println(\"file: \" + fileName);\n System.out.println(\"<<<<<<< SEARCH\");\n for (BugFix bugFix : bugFixes) {\n System.out.println(bugFix.getOriginalCode());\n }\n System.out.println(\"=======\");\n for (BugFix bugFix : bugFixes) {\n System.out.println(bugFix.getCorrectedCode());\n }\n System.out.println(\">>>>>>> REPLACE\");\n }\n\n public static class BugFix {\n private final String bugPosition;\n private final String correctedCode;\n private final String bugType;\n private final String explanation;\n private final String originalCode;\n \n public BugFix(String bugPosition, String correctedCode, \n String bugType, String explanation, String originalCode) {\n this.bugPosition = bugPosition;\n this.correctedCode = correctedCode;\n this.bugType = bugType;\n this.explanation = explanation;\n this.originalCode = originalCode;\n }\n\n public String getBugPosition() { return bugPosition; }\n public String getCorrectedCode() { return correctedCode; }\n public String getBugType() { return bugType; }\n public String getExplanation() { return explanation; }\n public String getOriginalCode() { return originalCode; }\n }\n }\n\n private static class OpenAIRequest {\n public String model = \"gpt-4\";\n public List<Message> messages;\n\n public OpenAIRequest(String prompt) {\n this.messages = List.of(new Message(\"user\", prompt));\n }\n\n static class Message {\n public String role;\n public String content;\n public Message(String role, String content) {\n this.role = role;\n this.content = content;\n }\n }\n }\n\n private static class ClaudeRequest {\n public String model = \"claude-3-opus-20240229\";\n public int max_tokens = 4000;\n public List<Message> messages;\n\n public ClaudeRequest(String prompt) {\n this.messages = List.of(new Message(prompt));\n }\n\n static class Message {\n public String role = \"user\";\n public List<Content> content;\n public Message(String prompt) {\n this.content = List.of(new Content(prompt));\n }\n static class Content {\n public String type = \"text\";\n public String text;\n public Content(String text) {\n this.text = text;\n }\n }\n }\n }\n\n private static class DeepSeekRequest {\n public String model = \"deepseek-coder\";\n public List<Message> messages;\n\n public DeepSeekRequest(String prompt) {\n this.messages = List.of(new Message(\"user\", prompt));\n }\n\n static class Message {\n public String role;\n public String content;\n public Message(String role, String content) {\n this.role = role;\n this.content = content;\n }\n }\n }\n}"}, "60f6b32a26142a1bf267878930d92e1c3785c62c": {"t": 1785760093.2593, "n": 11664, "h": "230c767c2f818c7d", "c": "package com.examensarbete;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Properties;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\npublic class AIClient {\n private static final ObjectMapper JSON_MAPPER = new ObjectMapper();\n private static final Properties API_KEYS = loadApiKeys();\n \n // Enhanced regex with flexible whitespace handling\n private static final Pattern BUG_BLOCK_PATTERN = Pattern.compile(\n \"BUG\\\\s+LOCATION:\\\\s*(.+?)\\\\n\" +\n \"BUG\\\\s+TYPE:\\\\s*(.+?)\\\\n\" +\n \"EXPLANATION:\\\\s*([\\\\s\\\\S]*?)\\\\n\" +\n \"COMPLETE\\\\s+FILE:\\\\s*```java\\\\s*([\\\\s\\\\S]*?)```\",\n Pattern.CASE_INSENSITIVE | Pattern.DOTALL\n );\n\n private static Properties loadApiKeys() {\n Properties properties = new Properties();\n try (InputStream in = AIClient.class.getClassLoader().getResourceAsStream(\"api-keys.properties\")) {\n if (in != null) {\n properties.load(in);\n }\n } catch (IOException e) {\n // Silent fail - will try environment variables\n }\n return properties;\n }\n\n public static String sendRequest(String model, String prompt, boolean verbose) throws Exception {\n if (verbose) {\n System.out.println(\"\\n=== SENDING REQUEST ===\");\n System.out.println(\"[DEBUG] Model: \" + model);\n System.out.println(\"[DEBUG] Prompt:\\n\" + prompt);\n }\n\n String apiKey = getApiKey(model);\n String apiUrl = getApiUrl(model);\n\n HttpClient client = HttpClient.newHttpClient();\n String requestBody = buildRequestBody(model, prompt);\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(apiUrl))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + apiKey)\n .POST(HttpRequest.BodyPublishers.ofString(requestBody))\n .build();\n\n HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());\n \n if (verbose) {\n System.out.println(\"\\n=== RAW RESPONSE ===\");\n System.out.println(\"[DEBUG] Status Code: \" + response.statusCode());\n System.out.println(\"[DEBUG] Response Body:\\n\" + response.body());\n }\n \n return response.body();\n }\n\n public static AIResponse parseResponse(String response, boolean verbose) throws Exception {\n if (verbose) {\n System.out.println(\"\\n=== PARSING RESPONSE ===\");\n }\n \n String content = extractContentFromApiResponse(response, verbose);\n \n if (content == null || content.trim().isEmpty()) {\n System.err.println(\"[ERROR] Empty or null content in response\");\n return null;\n }\n\n if (verbose) {\n System.out.println(\"[DEBUG] Extracted Content:\\n\" + content);\n }\n \n List<AIResponse.BugFix> bugFixes = new ArrayList<>();\n Matcher matcher = BUG_BLOCK_PATTERN.matcher(content);\n int blockCount = 0;\n \n while (matcher.find()) {\n blockCount++;\n try {\n String bugPosition = matcher.group(1).trim();\n String bugType = matcher.group(2).trim();\n String explanation = matcher.group(3).trim();\n String correctedCode = matcher.group(4).trim();\n\n // Basic validation\n if (correctedCode.isEmpty()) {\n if (verbose) {\n System.err.println(\"[WARN] Skipping block \" + blockCount + \": Empty code snippet detected\");\n }\n continue;\n }\n\n AIResponse.BugFix bugFix = new AIResponse.BugFix(\n bugPosition, \n correctedCode,\n bugType,\n explanation\n );\n bugFixes.add(bugFix);\n \n if (verbose) {\n System.out.println(\"[DEBUG] Parsed Bug Fix \" + blockCount + \":\");\n System.out.println(\"- Position: \" + bugPosition);\n System.out.println(\"- Type: \" + bugType);\n System.out.println(\"- Explanation: \" + explanation);\n }\n \n } catch (Exception e) {\n System.err.println(\"[ERROR] Failed to parse bug block \" + blockCount + \": \" + e.getMessage());\n }\n }\n\n if (!bugFixes.isEmpty()) {\n if (verbose) {\n System.out.println(\"[INFO] Successfully parsed \" + bugFixes.size() + \" bug fixes out of \" + blockCount + \" blocks\");\n }\n return new AIResponse(bugFixes);\n }\n \n if (verbose) {\n System.out.println(\"[ERROR] Failed to parse any bug fixes from \" + blockCount + \" potential blocks\");\n System.out.println(\"[DEBUG] Full content for analysis:\\n\" + content);\n }\n return null;\n }\n\n private static String extractContentFromApiResponse(String response, boolean verbose) throws Exception {\n if (verbose) {\n System.out.println(\"\\n=== EXTRACTING CONTENT ===\");\n }\n \n try {\n JsonNode root = JSON_MAPPER.readTree(response);\n if (verbose) {\n System.out.println(\"[DEBUG] JSON Structure:\\n\" + root.toPrettyString());\n }\n\n if (root.has(\"choices\")) {\n JsonNode choice = root.get(\"choices\").get(0);\n if (choice.has(\"message\") && choice.get(\"message\").has(\"content\")) {\n String content = choice.get(\"message\").get(\"content\").asText();\n if (verbose) {\n System.out.println(\"[DEBUG] Extracted OpenAI content\");\n }\n return content;\n }\n }\n\n if (root.has(\"content\")) {\n StringBuilder contentBuilder = new StringBuilder();\n for (JsonNode item : root.get(\"content\")) {\n if (item.has(\"text\")) {\n contentBuilder.append(item.get(\"text\").asText());\n }\n }\n if (verbose) {\n System.out.println(\"[DEBUG] Extracted Anthropic content\");\n }\n return contentBuilder.toString();\n }\n\n if (root.has(\"response\")) {\n if (verbose) {\n System.out.println(\"[DEBUG] Extracted DeepSeek content\");\n }\n return root.get(\"response\").asText();\n }\n\n if (verbose) {\n System.out.println(\"[WARN] Unknown response format, returning raw response\");\n }\n return response;\n \n } catch (Exception e) {\n System.err.println(\"[ERROR] Failed to parse JSON: \" + e.getMessage());\n if (verbose) {\n System.out.println(\"[DEBUG] Raw response:\\n\" + response);\n }\n return response;\n }\n }\n\n private static String getApiKey(String model) {\n String keyName = model.toUpperCase() + \"_API_KEY\";\n String apiKey = API_KEYS.getProperty(keyName, System.getenv(keyName));\n \n if (apiKey == null || apiKey.isBlank()) {\n throw new RuntimeException(\"Missing API key for \" + model);\n }\n return apiKey;\n }\n\n private static String getApiUrl(String model) {\n return switch (model.toLowerCase()) {\n case \"openai\" -> \"https://api.openai.com/v1/chat/completions\";\n case \"claude\" -> \"https://api.anthropic.com/v1/messages\";\n case \"deepseek\" -> \"https://api.deepseek.com/v1/chat/completions\";\n default -> throw new IllegalArgumentException(\"Unsupported model: \" + model);\n };\n }\n\n private static String buildRequestBody(String model, String prompt) throws Exception {\n return switch (model.toLowerCase()) {\n case \"openai\" -> JSON_MAPPER.writeValueAsString(new OpenAIRequest(prompt));\n case \"claude\" -> JSON_MAPPER.writeValueAsString(new ClaudeRequest(prompt));\n case \"deepseek\" -> JSON_MAPPER.writeValueAsString(new DeepSeekRequest(prompt));\n default -> throw new IllegalArgumentException(\"Unsupported model: \" + model);\n };\n }\n\n public static class AIResponse {\n private final List<BugFix> bugFixes;\n \n public AIResponse(List<BugFix> bugFixes) {\n this.bugFixes = bugFixes;\n }\n\n public List<BugFix> getBugFixes() { \n return bugFixes; \n }\n\n public static class BugFix {\n private final String bugPosition;\n private final String correctedCode;\n private final String bugType;\n private final String explanation;\n \n public BugFix(String bugPosition, String correctedCode, \n String bugType, String explanation) {\n this.bugPosition = bugPosition;\n this.correctedCode = correctedCode;\n this.bugType = bugType;\n this.explanation = explanation;\n }\n\n public String getBugPosition() { return bugPosition; }\n public String getCorrectedCode() { return correctedCode; }\n public String getBugType() { return bugType; }\n public String getExplanation() { return explanation; }\n }\n }\n\n private static class OpenAIRequest {\n public String model = \"gpt-4\";\n public List<Message> messages;\n\n public OpenAIRequest(String prompt) {\n this.messages = List.of(new Message(\"user\", prompt));\n }\n\n static class Message {\n public String role;\n public String content;\n public Message(String role, String content) {\n this.role = role;\n this.content = content;\n }\n }\n }\n\n private static class ClaudeRequest {\n public String model = \"claude-3-opus-20240229\";\n public int max_tokens = 4000;\n public List<Message> messages;\n\n public ClaudeRequest(String prompt) {\n this.messages = List.of(new Message(prompt));\n }\n\n static class Message {\n public String role = \"user\";\n public List<Content> content;\n public Message(String prompt) {\n this.content = List.of(new Content(prompt));\n }\n static class Content {\n public String type = \"text\";\n public String text;\n public Content(String text) {\n this.text = text;\n }\n }\n }\n }\n\n private static class DeepSeekRequest {\n public String model = \"deepseek-coder\";\n public List<Message> messages;\n\n public DeepSeekRequest(String prompt) {\n this.messages = List.of(new Message(\"user\", prompt));\n }\n\n static class Message {\n public String role;\n public String content;\n public Message(String role, String content) {\n this.role = role;\n this.content = content;\n }\n }\n }\n}"}, "100df320e223b6233be643025c3343529eaeef30": {"t": 1785760094.6942842, "n": 10966, "h": "050e129edd6b2c59", "c": "package com.examensarbete;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Properties;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\npublic class AIClient {\n private static final ObjectMapper JSON_MAPPER = new ObjectMapper();\n private static final Properties API_KEYS = loadApiKeys();\n \n // Enhanced regex with flexible whitespace handling\n private static final Pattern BUG_BLOCK_PATTERN = Pattern.compile(\n \"BUG\\\\s+LOCATION:\\\\s*(.+?)\\\\n\" +\n \"BUG\\\\s+TYPE:\\\\s*(.+?)\\\\n\" +\n \"EXPLANATION:\\\\s*([\\\\s\\\\S]*?)\\\\n\" +\n \"COMPLETE\\\\s+FILE:\\\\s*```java\\\\s*([\\\\s\\\\S]*?)```\",\n Pattern.CASE_INSENSITIVE | Pattern.DOTALL\n );\n\n private static Properties loadApiKeys() {\n Properties properties = new Properties();\n try (InputStream in = AIClient.class.getClassLoader().getResourceAsStream(\"api-keys.properties\")) {\n if (in != null) {\n properties.load(in);\n System.out.println(\"[DEBUG] Loaded API keys from properties file\");\n }\n } catch (IOException e) {\n System.out.println(\"[WARN] No api-keys.properties found\");\n }\n return properties;\n }\n\n public static String sendRequest(String model, String prompt) throws Exception {\n System.out.println(\"\\n=== SENDING REQUEST ===\");\n System.out.println(\"[DEBUG] Model: \" + model);\n System.out.println(\"[DEBUG] Prompt:\\n\" + prompt);\n\n String apiKey = getApiKey(model);\n String apiUrl = getApiUrl(model);\n\n HttpClient client = HttpClient.newHttpClient();\n String requestBody = buildRequestBody(model, prompt);\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(apiUrl))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + apiKey)\n .POST(HttpRequest.BodyPublishers.ofString(requestBody))\n .build();\n\n HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());\n \n System.out.println(\"\\n=== RAW RESPONSE ===\");\n System.out.println(\"[DEBUG] Status Code: \" + response.statusCode());\n System.out.println(\"[DEBUG] Response Body:\\n\" + response.body());\n \n return response.body();\n }\n\n public static AIResponse parseResponse(String response) throws Exception {\n System.out.println(\"\\n=== PARSING RESPONSE ===\");\n String content = extractContentFromApiResponse(response);\n \n if (content == null || content.trim().isEmpty()) {\n System.out.println(\"[ERROR] Empty or null content in response\");\n return null;\n }\n\n System.out.println(\"[DEBUG] Extracted Content:\\n\" + content);\n \n List<AIResponse.BugFix> bugFixes = new ArrayList<>();\n Matcher matcher = BUG_BLOCK_PATTERN.matcher(content);\n int blockCount = 0;\n \n while (matcher.find()) {\n blockCount++;\n try {\n String bugPosition = matcher.group(1).trim();\n String bugType = matcher.group(2).trim();\n String explanation = matcher.group(3).trim();\n String correctedCode = matcher.group(4).trim();\n\n // Basic validation\n if (correctedCode.isEmpty()) {\n System.err.println(\"[WARN] Skipping block \" + blockCount + \": Empty code snippet detected\");\n continue;\n }\n\n AIResponse.BugFix bugFix = new AIResponse.BugFix(\n bugPosition, \n correctedCode,\n bugType,\n explanation\n );\n bugFixes.add(bugFix);\n \n System.out.println(\"[DEBUG] Parsed Bug Fix \" + blockCount + \":\");\n System.out.println(\"- Position: \" + bugPosition);\n System.out.println(\"- Type: \" + bugType);\n System.out.println(\"- Explanation: \" + explanation);\n \n } catch (Exception e) {\n System.err.println(\"[ERROR] Failed to parse bug block \" + blockCount + \": \" + e.getMessage());\n }\n }\n\n if (!bugFixes.isEmpty()) {\n System.out.println(\"[INFO] Successfully parsed \" + bugFixes.size() + \" bug fixes out of \" + blockCount + \" blocks\");\n return new AIResponse(bugFixes);\n }\n \n System.out.println(\"[ERROR] Failed to parse any bug fixes from \" + blockCount + \" potential blocks\");\n System.out.println(\"[DEBUG] Full content for analysis:\\n\" + content);\n return null;\n }\n\n private static String extractContentFromApiResponse(String response) throws Exception {\n System.out.println(\"\\n=== EXTRACTING CONTENT ===\");\n try {\n JsonNode root = JSON_MAPPER.readTree(response);\n System.out.println(\"[DEBUG] JSON Structure:\\n\" + root.toPrettyString());\n\n if (root.has(\"choices\")) {\n JsonNode choice = root.get(\"choices\").get(0);\n if (choice.has(\"message\") && choice.get(\"message\").has(\"content\")) {\n String content = choice.get(\"message\").get(\"content\").asText();\n System.out.println(\"[DEBUG] Extracted OpenAI content\");\n return content;\n }\n }\n\n if (root.has(\"content\")) {\n StringBuilder contentBuilder = new StringBuilder();\n for (JsonNode item : root.get(\"content\")) {\n if (item.has(\"text\")) {\n contentBuilder.append(item.get(\"text\").asText());\n }\n }\n System.out.println(\"[DEBUG] Extracted Anthropic content\");\n return contentBuilder.toString();\n }\n\n if (root.has(\"response\")) {\n System.out.println(\"[DEBUG] Extracted DeepSeek content\");\n return root.get(\"response\").asText();\n }\n\n System.out.println(\"[WARN] Unknown response format, returning raw response\");\n return response;\n \n } catch (Exception e) {\n System.out.println(\"[ERROR] Failed to parse JSON: \" + e.getMessage());\n System.out.println(\"[DEBUG] Raw response:\\n\" + response);\n return response;\n }\n }\n\n private static String getApiKey(String model) {\n String keyName = model.toUpperCase() + \"_API_KEY\";\n String apiKey = API_KEYS.getProperty(keyName, System.getenv(keyName));\n \n if (apiKey == null || apiKey.isBlank()) {\n throw new RuntimeException(\"Missing API key for \" + model);\n }\n return apiKey;\n }\n\n private static String getApiUrl(String model) {\n return switch (model.toLowerCase()) {\n case \"openai\" -> \"https://api.openai.com/v1/chat/completions\";\n case \"claude\" -> \"https://api.anthropic.com/v1/messages\";\n case \"deepseek\" -> \"https://api.deepseek.com/v1/chat/completions\";\n default -> throw new IllegalArgumentException(\"Unsupported model: \" + model);\n };\n }\n\n private static String buildRequestBody(String model, String prompt) throws Exception {\n return switch (model.toLowerCase()) {\n case \"openai\" -> JSON_MAPPER.writeValueAsString(new OpenAIRequest(prompt));\n case \"claude\" -> JSON_MAPPER.writeValueAsString(new ClaudeRequest(prompt));\n case \"deepseek\" -> JSON_MAPPER.writeValueAsString(new DeepSeekRequest(prompt));\n default -> throw new IllegalArgumentException(\"Unsupported model: \" + model);\n };\n }\n\n public static class AIResponse {\n private final List<BugFix> bugFixes;\n \n public AIResponse(List<BugFix> bugFixes) {\n this.bugFixes = bugFixes;\n }\n\n public List<BugFix> getBugFixes() { \n return bugFixes; \n }\n\n public static class BugFix {\n private final String bugPosition;\n private final String correctedCode;\n private final String bugType;\n private final String explanation;\n \n public BugFix(String bugPosition, String correctedCode, \n String bugType, String explanation) {\n this.bugPosition = bugPosition;\n this.correctedCode = correctedCode;\n this.bugType = bugType;\n this.explanation = explanation;\n }\n\n public String getBugPosition() { return bugPosition; }\n public String getCorrectedCode() { return correctedCode; }\n public String getBugType() { return bugType; }\n public String getExplanation() { return explanation; }\n }\n }\n\n private static class OpenAIRequest {\n public String model = \"gpt-4\";\n public List<Message> messages;\n\n public OpenAIRequest(String prompt) {\n this.messages = List.of(new Message(\"user\", prompt));\n }\n\n static class Message {\n public String role;\n public String content;\n public Message(String role, String content) {\n this.role = role;\n this.content = content;\n }\n }\n }\n\n private static class ClaudeRequest {\n public String model = \"claude-3-opus-20240229\";\n public int max_tokens = 4000;\n public List<Message> messages;\n\n public ClaudeRequest(String prompt) {\n this.messages = List.of(new Message(prompt));\n }\n\n static class Message {\n public String role = \"user\";\n public List<Content> content;\n public Message(String prompt) {\n this.content = List.of(new Content(prompt));\n }\n static class Content {\n public String type = \"text\";\n public String text;\n public Content(String text) {\n this.text = text;\n }\n }\n }\n }\n\n private static class DeepSeekRequest {\n public String model = \"deepseek-coder\";\n public List<Message> messages;\n\n public DeepSeekRequest(String prompt) {\n this.messages = List.of(new Message(\"user\", prompt));\n }\n\n static class Message {\n public String role;\n public String content;\n public Message(String role, String content) {\n this.role = role;\n this.content = content;\n }\n }\n }\n}"}, "5b2d252a1cb3ef8144bda29f96a0a8d952111175": {"t": 1785760095.9421856, "n": 9470, "h": "36d173752ffb1b66", "c": "package com.examensarbete;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Properties;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\npublic class AIClient {\n private static final ObjectMapper JSON_MAPPER = new ObjectMapper();\n private static final Properties API_KEYS = loadApiKeys();\n private static final Pattern BUG_POSITION_PATTERN = \n Pattern.compile(\"Bug Position:\\\\s*(\\\\d+)-(\\\\d+)\", Pattern.CASE_INSENSITIVE);\n \nprivate static final Pattern CORRECTED_CODE_PATTERN = \n Pattern.compile(\"Corrected Code:\\\\s*```java\\\\s*([\\\\s\\\\S]*?)```\", Pattern.CASE_INSENSITIVE | Pattern.DOTALL);\n private static Properties loadApiKeys() {\n Properties properties = new Properties();\n try (InputStream in = AIClient.class.getClassLoader().getResourceAsStream(\"api-keys.properties\")) {\n if (in != null) {\n properties.load(in);\n System.out.println(\"[DEBUG] Loaded API keys from properties file\");\n }\n } catch (IOException e) {\n System.out.println(\"[WARN] No api-keys.properties found\");\n }\n return properties;\n }\n\n public static String sendRequest(String model, String prompt) throws Exception {\n System.out.println(\"\\n=== SENDING REQUEST ===\");\n System.out.println(\"[DEBUG] Model: \" + model);\n System.out.println(\"[DEBUG] Prompt:\\n\" + prompt);\n\n String apiKey = getApiKey(model);\n String apiUrl = getApiUrl(model);\n\n HttpClient client = HttpClient.newHttpClient();\n String requestBody = buildRequestBody(model, prompt);\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(apiUrl))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + apiKey)\n .POST(HttpRequest.BodyPublishers.ofString(requestBody))\n .build();\n\n HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());\n \n System.out.println(\"\\n=== RAW RESPONSE ===\");\n System.out.println(\"[DEBUG] Status Code: \" + response.statusCode());\n System.out.println(\"[DEBUG] Response Body:\\n\" + response.body());\n \n return response.body();\n }\n\n public static AIResponse parseResponse(String response) throws Exception {\n System.out.println(\"\\n=== PARSING RESPONSE ===\");\n String content = extractContentFromApiResponse(response);\n \n if (content == null || content.isBlank()) {\n System.out.println(\"[ERROR] Empty content in response\");\n return null;\n }\n\n System.out.println(\"[DEBUG] Extracted Content:\\n\" + content);\n\n if (content.toLowerCase().contains(\"no bugs found\") || \n content.toLowerCase().contains(\"no issues\")) {\n System.out.println(\"[INFO] AI reported no bugs found\");\n return null;\n }\n\n Matcher posMatcher = BUG_POSITION_PATTERN.matcher(content);\n Matcher codeMatcher = CORRECTED_CODE_PATTERN.matcher(content);\n\n boolean hasPosition = posMatcher.find();\n boolean hasCode = codeMatcher.find();\n\n System.out.println(\"[DEBUG] Position match: \" + hasPosition);\n System.out.println(\"[DEBUG] Code match: \" + hasCode);\n\n if (hasPosition && hasCode) {\n String bugPosition = posMatcher.group(1) + \"-\" + posMatcher.group(2);\n String correctedCode = codeMatcher.group(1).trim();\n \n System.out.println(\"[DEBUG] Parsed Position: \" + bugPosition);\n System.out.println(\"[DEBUG] Parsed Code:\\n\" + correctedCode);\n \n return new AIResponse(bugPosition, correctedCode);\n }\n\n System.out.println(\"[ERROR] Failed to parse required elements\");\n System.out.println(\"[DEBUG] Full content for analysis:\\n\" + content);\n return null;\n }\n\n private static String extractContentFromApiResponse(String response) throws Exception {\n System.out.println(\"\\n=== EXTRACTING CONTENT ===\");\n try {\n JsonNode root = JSON_MAPPER.readTree(response);\n System.out.println(\"[DEBUG] JSON Structure:\\n\" + root.toPrettyString());\n\n if (root.has(\"choices\")) {\n JsonNode choice = root.get(\"choices\").get(0);\n if (choice.has(\"message\") && choice.get(\"message\").has(\"content\")) {\n String content = choice.get(\"message\").get(\"content\").asText();\n System.out.println(\"[DEBUG] Extracted OpenAI content\");\n return content;\n }\n }\n\n if (root.has(\"content\")) {\n StringBuilder contentBuilder = new StringBuilder();\n for (JsonNode item : root.get(\"content\")) {\n if (item.has(\"text\")) {\n contentBuilder.append(item.get(\"text\").asText());\n }\n }\n System.out.println(\"[DEBUG] Extracted Anthropic content\");\n return contentBuilder.toString();\n }\n\n if (root.has(\"response\")) {\n System.out.println(\"[DEBUG] Extracted DeepSeek content\");\n return root.get(\"response\").asText();\n }\n\n System.out.println(\"[WARN] Unknown response format\");\n return response;\n \n } catch (Exception e) {\n System.out.println(\"[ERROR] Failed to parse JSON: \" + e.getMessage());\n System.out.println(\"[DEBUG] Raw response:\\n\" + response);\n return response;\n }\n }\n\n private static String getApiKey(String model) {\n String keyName = model.toUpperCase() + \"_API_KEY\";\n String apiKey = API_KEYS.getProperty(keyName, System.getenv(keyName));\n \n if (apiKey == null || apiKey.isBlank()) {\n throw new RuntimeException(\"Missing API key for \" + model);\n }\n return apiKey;\n }\n\n private static String getApiUrl(String model) {\n return switch (model.toLowerCase()) {\n case \"openai\" -> \"https://api.openai.com/v1/chat/completions\";\n case \"claude\" -> \"https://api.anthropic.com/v1/messages\";\n case \"deepseek\" -> \"https://api.deepseek.com/v1/chat/completions\";\n default -> throw new IllegalArgumentException(\"Unsupported model: \" + model);\n };\n }\n\n private static String buildRequestBody(String model, String prompt) throws Exception {\n return switch (model.toLowerCase()) {\n case \"openai\" -> JSON_MAPPER.writeValueAsString(new OpenAIRequest(prompt));\n case \"claude\" -> JSON_MAPPER.writeValueAsString(new ClaudeRequest(prompt));\n case \"deepseek\" -> JSON_MAPPER.writeValueAsString(new DeepSeekRequest(prompt));\n default -> throw new IllegalArgumentException(\"Unsupported model: \" + model);\n };\n }\n\n public static class AIResponse {\n private final String bugPosition;\n private final String correctedCode;\n\n public AIResponse(String bugPosition, String correctedCode) {\n this.bugPosition = bugPosition;\n this.correctedCode = correctedCode;\n }\n\n public String getBugPosition() { return bugPosition; }\n public String getCorrectedCode() { return correctedCode; }\n }\n\n private static class OpenAIRequest {\n public String model = \"gpt-4\";\n public List<Message> messages;\n\n public OpenAIRequest(String prompt) {\n this.messages = List.of(new Message(\"user\", prompt));\n }\n\n static class Message {\n public String role;\n public String content;\n public Message(String role, String content) {\n this.role = role;\n this.content = content;\n }\n }\n }\n\n private static class ClaudeRequest {\n public String model = \"claude-3-opus-20240229\";\n public int max_tokens = 4000;\n public List<Message> messages;\n\n public ClaudeRequest(String prompt) {\n this.messages = List.of(new Message(prompt));\n }\n\n static class Message {\n public String role = \"user\";\n public List<Content> content;\n public Message(String prompt) {\n this.content = List.of(new Content(prompt));\n }\n static class Content {\n public String type = \"text\";\n public String text;\n public Content(String text) {\n this.text = text;\n }\n }\n }\n }\n\n private static class DeepSeekRequest {\n public String model = \"deepseek-coder\";\n public List<Message> messages;\n\n public DeepSeekRequest(String prompt) {\n this.messages = List.of(new Message(\"user\", prompt));\n }\n\n static class Message {\n public String role;\n public String content;\n public Message(String role, String content) {\n this.role = role;\n this.content = content;\n }\n }\n }\n}"}, "606d87b569abb6c347f87de0d59d945347e9e54b": {"t": 1785760097.1915975, "n": 10459, "h": "4faa07357e50c3cd", "c": "package com.examensarbete;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Properties;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * Client for interacting with AI model APIs.\n */\npublic class AIClient {\n private static final ObjectMapper JSON_MAPPER = new ObjectMapper();\n private static final Properties API_KEYS = loadApiKeys();\n \n // Regex patterns for extracting information from AI responses\n private static final Pattern BUG_POSITION_PATTERN = Pattern.compile(\"Bug Position:\\\\s*(\\\\d+)-(\\\\d+)\", Pattern.CASE_INSENSITIVE);\n private static final Pattern CORRECTED_CODE_PATTERN = Pattern.compile(\"Corrected Code:\\\\s*```java\\\\s*([\\\\s\\\\S]*?)```\", Pattern.CASE_INSENSITIVE);\n \n /**\n * Loads API keys from properties file or environment variables.\n */\n private static Properties loadApiKeys() {\n Properties properties = new Properties();\n \n // Try to load from properties file first\n try (InputStream in = AIClient.class.getClassLoader().getResourceAsStream(\"api-keys.properties\")) {\n if (in != null) {\n properties.load(in);\n System.out.println(\"Loaded API keys from properties file\");\n }\n } catch (IOException e) {\n System.out.println(\"No api-keys.properties file found, will use environment variables\");\n }\n \n return properties;\n }\n \n /**\n * Sends a request to the AI model's API.\n * \n * @param model The AI model to use\n * @param prompt The prompt to send\n * @return The raw response from the AI model\n * @throws Exception If there's an error communicating with the API\n */\n public static String sendRequest(String model, String prompt) throws Exception {\n String apiKey = getApiKey(model);\n String apiUrl = getApiUrl(model);\n \n HttpClient client = HttpClient.newHttpClient();\n String requestBody = buildRequestBody(model, prompt);\n \n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(apiUrl))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + apiKey)\n .POST(HttpRequest.BodyPublishers.ofString(requestBody))\n .build();\n \n System.out.println(\"Sending request to AI model...\");\n HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());\n \n if (response.statusCode() >= 400) {\n throw new RuntimeException(\"API request failed with status code \" + response.statusCode() + \": \" + response.body());\n }\n \n return response.body();\n }\n \n /**\n * Parses the AI response to extract information.\n * \n * @param response The raw AI response\n * @return Structured response object\n */\n public static AIResponse parseResponse(String response) throws Exception {\n // Parse the response to extract content from JSON if necessary\n String content = extractContentFromApiResponse(response);\n \n // Extract bug positions and corrected code snippets\n List<String> bugPositions = new ArrayList<>();\n List<String> correctedCodeSnippets = new ArrayList<>();\n \n Matcher posMatcher = BUG_POSITION_PATTERN.matcher(content);\n while (posMatcher.find()) {\n bugPositions.add(posMatcher.group(1) + \"-\" + posMatcher.group(2));\n }\n \n Matcher codeMatcher = CORRECTED_CODE_PATTERN.matcher(content);\n while (codeMatcher.find()) {\n correctedCodeSnippets.add(codeMatcher.group(1).trim());\n }\n \n // For now, we only handle the first bug found\n if (bugPositions.isEmpty() || correctedCodeSnippets.isEmpty()) {\n throw new RuntimeException(\"Could not extract bug position and corrected code from AI response\");\n }\n \n return new AIResponse(bugPositions.get(0), correctedCodeSnippets.get(0));\n }\n \n /**\n * Extracts content from API response based on the model's response format.\n */\n private static String extractContentFromApiResponse(String response) throws Exception {\n JsonNode root = JSON_MAPPER.readTree(response);\n \n // Handle OpenAI format\n if (root.has(\"choices\") && root.get(\"choices\").isArray() && root.get(\"choices\").size() > 0) {\n JsonNode message = root.get(\"choices\").get(0).get(\"message\");\n if (message != null && message.has(\"content\")) {\n return message.get(\"content\").asText();\n }\n }\n \n // Handle Anthropic Claude format\n if (root.has(\"content\") && root.get(\"content\").isArray()) {\n StringBuilder contentBuilder = new StringBuilder();\n JsonNode contentArray = root.get(\"content\");\n for (JsonNode item : contentArray) {\n if (item.has(\"text\")) {\n contentBuilder.append(item.get(\"text\").asText());\n }\n }\n if (contentBuilder.length() > 0) {\n return contentBuilder.toString();\n }\n }\n \n // Handle DeepSeek format\n if (root.has(\"response\")) {\n return root.get(\"response\").asText();\n }\n \n // Fallback - just return the raw response if we can't parse it\n return response;\n }\n \n /**\n * Gets the API key for the specified model.\n */\n private static String getApiKey(String model) {\n // Convert model name to uppercase for naming consistency\n String keyName = model.toUpperCase() + \"_API_KEY\";\n \n // Try to get from properties file first\n String apiKey = API_KEYS.getProperty(keyName);\n \n // If not found in properties, try environment variable\n if (apiKey == null || apiKey.isEmpty()) {\n apiKey = System.getenv(keyName);\n }\n \n if (apiKey == null || apiKey.isEmpty()) {\n throw new RuntimeException(\"API key not found. Please set it in api-keys.properties or as the \" \n + keyName + \" environment variable.\");\n }\n \n return apiKey;\n }\n \n /**\n * Gets the API URL for the specified model.\n */\n private static String getApiUrl(String model) {\n model = model.toLowerCase();\n \n switch (model) {\n case \"openai\":\n return \"https://api.openai.com/v1/chat/completions\";\n case \"claude\":\n return \"https://api.anthropic.com/v1/messages\";\n case \"deepseek\":\n return \"https://api.deepseek.com/v1/chat/completions\";\n default:\n throw new IllegalArgumentException(\"Unsupported AI model: \" + model);\n }\n }\n \n /**\n * Builds the request body for the specified model and prompt.\n */\n private static String buildRequestBody(String model, String prompt) throws Exception {\n model = model.toLowerCase();\n \n switch (model) {\n case \"openai\":\n return JSON_MAPPER.writeValueAsString(new OpenAIRequest(prompt));\n case \"claude\":\n return JSON_MAPPER.writeValueAsString(new ClaudeRequest(prompt));\n case \"deepseek\":\n return JSON_MAPPER.writeValueAsString(new DeepSeekRequest(prompt));\n default:\n throw new IllegalArgumentException(\"Unsupported AI model: \" + model);\n }\n }\n \n /**\n * Class representing the structured response from the AI.\n */\n public static class AIResponse {\n private final String bugPosition;\n private final String correctedCode;\n \n public AIResponse(String bugPosition, String correctedCode) {\n this.bugPosition = bugPosition;\n this.correctedCode = correctedCode;\n }\n \n public String getBugPosition() {\n return bugPosition;\n }\n \n public String getCorrectedCode() {\n return correctedCode;\n }\n }\n \n /**\n * Classes for different AI model request formats\n */\n private static class OpenAIRequest {\n public String model = \"gpt-4\";\n public List<Message> messages;\n \n public OpenAIRequest(String prompt) {\n this.messages = List.of(new Message(\"user\", prompt));\n }\n \n static class Message {\n public String role;\n public String content;\n \n public Message(String role, String content) {\n this.role = role;\n this.content = content;\n }\n }\n }\n \n private static class ClaudeRequest {\n public String model = \"claude-3-opus-20240229\";\n public String max_tokens = \"4000\";\n public List<Message> messages;\n \n public ClaudeRequest(String prompt) {\n this.messages = List.of(new Message(prompt));\n }\n \n static class Message {\n public String role = \"user\";\n public List<Content> content;\n \n public Message(String prompt) {\n this.content = List.of(new Content(prompt));\n }\n \n static class Content {\n public String type = \"text\";\n public String text;\n \n public Content(String text) {\n this.text = text;\n }\n }\n }\n }\n \n private static class DeepSeekRequest {\n public String model = \"deepseek-coder\";\n public List<Message> messages;\n \n public DeepSeekRequest(String prompt) {\n this.messages = List.of(new Message(\"user\", prompt));\n }\n \n static class Message {\n public String role;\n public String content;\n \n public Message(String role, String content) {\n this.role = role;\n this.content = content;\n }\n }\n }\n} "}}}}