From 432c482a56a2454e6fa121b5653f0816ac843ed0 Mon Sep 17 00:00:00 2001 From: Franz Zieris Date: Mon, 9 Mar 2026 09:53:57 +0100 Subject: [PATCH 1/6] Move services into sub-packages --- .../java/com/cerca/controller/MainController.java | 14 +++++++------- .../service/{ => extraction}/CermineService.java | 2 +- .../service/{ => lookup}/CrossrefService.java | 3 ++- .../service/{ => lookup}/OpenAlexService.java | 5 +++-- .../{ => lookup}/SemanticScholarService.java | 3 ++- .../cerca/service/{ => lookup}/ZenodoService.java | 3 ++- .../com/cerca/service/{ => report}/CsvService.java | 2 +- .../cerca/service/{ => report}/ReportService.java | 2 +- .../extraction/CermineServiceTest.java} | 5 ++--- 9 files changed, 21 insertions(+), 18 deletions(-) rename src/main/java/com/cerca/service/{ => extraction}/CermineService.java (98%) rename src/main/java/com/cerca/service/{ => lookup}/CrossrefService.java (98%) rename src/main/java/com/cerca/service/{ => lookup}/OpenAlexService.java (98%) rename src/main/java/com/cerca/service/{ => lookup}/SemanticScholarService.java (99%) rename src/main/java/com/cerca/service/{ => lookup}/ZenodoService.java (98%) rename src/main/java/com/cerca/service/{ => report}/CsvService.java (98%) rename src/main/java/com/cerca/service/{ => report}/ReportService.java (99%) rename src/test/java/com/cerca/{utils/PDFReadTest.java => service/extraction/CermineServiceTest.java} (95%) diff --git a/src/main/java/com/cerca/controller/MainController.java b/src/main/java/com/cerca/controller/MainController.java index 336ac5b..458b54d 100644 --- a/src/main/java/com/cerca/controller/MainController.java +++ b/src/main/java/com/cerca/controller/MainController.java @@ -11,15 +11,15 @@ import java.util.concurrent.CompletionException; import com.cerca.model.ReferenceItem; -import com.cerca.service.CermineService; +import com.cerca.service.extraction.CermineService; import com.cerca.service.ConfigService; -import com.cerca.service.CrossrefService; -import com.cerca.service.CsvService; +import com.cerca.service.lookup.CrossrefService; +import com.cerca.service.report.CsvService; import com.cerca.service.LogService; -import com.cerca.service.OpenAlexService; -import com.cerca.service.ReportService; -import com.cerca.service.SemanticScholarService; -import com.cerca.service.ZenodoService; +import com.cerca.service.lookup.OpenAlexService; +import com.cerca.service.report.ReportService; +import com.cerca.service.lookup.SemanticScholarService; +import com.cerca.service.lookup.ZenodoService; import com.cerca.utils.ReferenceParser; import com.cerca.view.MainView; diff --git a/src/main/java/com/cerca/service/CermineService.java b/src/main/java/com/cerca/service/extraction/CermineService.java similarity index 98% rename from src/main/java/com/cerca/service/CermineService.java rename to src/main/java/com/cerca/service/extraction/CermineService.java index 243eeb6..329ff1e 100644 --- a/src/main/java/com/cerca/service/CermineService.java +++ b/src/main/java/com/cerca/service/extraction/CermineService.java @@ -1,4 +1,4 @@ -package com.cerca.service; +package com.cerca.service.extraction; import com.cerca.model.ReferenceItem; import pl.edu.icm.cermine.ContentExtractor; diff --git a/src/main/java/com/cerca/service/CrossrefService.java b/src/main/java/com/cerca/service/lookup/CrossrefService.java similarity index 98% rename from src/main/java/com/cerca/service/CrossrefService.java rename to src/main/java/com/cerca/service/lookup/CrossrefService.java index c713553..207b069 100644 --- a/src/main/java/com/cerca/service/CrossrefService.java +++ b/src/main/java/com/cerca/service/lookup/CrossrefService.java @@ -1,4 +1,4 @@ -package com.cerca.service; +package com.cerca.service.lookup; import java.net.URI; import java.net.URLEncoder; @@ -8,6 +8,7 @@ import java.nio.charset.StandardCharsets; import com.cerca.model.ReferenceItem; +import com.cerca.service.LogService; import com.google.gson.JsonObject; import com.google.gson.JsonParser; diff --git a/src/main/java/com/cerca/service/OpenAlexService.java b/src/main/java/com/cerca/service/lookup/OpenAlexService.java similarity index 98% rename from src/main/java/com/cerca/service/OpenAlexService.java rename to src/main/java/com/cerca/service/lookup/OpenAlexService.java index 03d3fbf..b5f9a00 100644 --- a/src/main/java/com/cerca/service/OpenAlexService.java +++ b/src/main/java/com/cerca/service/lookup/OpenAlexService.java @@ -1,6 +1,7 @@ -package com.cerca.service; +package com.cerca.service.lookup; import com.cerca.model.ReferenceItem; +import com.cerca.service.LogService; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.JsonParser; @@ -19,7 +20,7 @@ public class OpenAlexService { private final HttpClient client; - private final LogService logger; + private final LogService logger; private String email; diff --git a/src/main/java/com/cerca/service/SemanticScholarService.java b/src/main/java/com/cerca/service/lookup/SemanticScholarService.java similarity index 99% rename from src/main/java/com/cerca/service/SemanticScholarService.java rename to src/main/java/com/cerca/service/lookup/SemanticScholarService.java index 9ca2a9e..0ca18dc 100644 --- a/src/main/java/com/cerca/service/SemanticScholarService.java +++ b/src/main/java/com/cerca/service/lookup/SemanticScholarService.java @@ -1,4 +1,4 @@ -package com.cerca.service; +package com.cerca.service.lookup; import java.net.URI; import java.net.URLEncoder; @@ -9,6 +9,7 @@ import java.time.Duration; import com.cerca.model.ReferenceItem; +import com.cerca.service.LogService; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.JsonParser; diff --git a/src/main/java/com/cerca/service/ZenodoService.java b/src/main/java/com/cerca/service/lookup/ZenodoService.java similarity index 98% rename from src/main/java/com/cerca/service/ZenodoService.java rename to src/main/java/com/cerca/service/lookup/ZenodoService.java index 04cd03d..01033e0 100644 --- a/src/main/java/com/cerca/service/ZenodoService.java +++ b/src/main/java/com/cerca/service/lookup/ZenodoService.java @@ -1,6 +1,7 @@ -package com.cerca.service; +package com.cerca.service.lookup; import com.cerca.model.ReferenceItem; +import com.cerca.service.LogService; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.JsonParser; diff --git a/src/main/java/com/cerca/service/CsvService.java b/src/main/java/com/cerca/service/report/CsvService.java similarity index 98% rename from src/main/java/com/cerca/service/CsvService.java rename to src/main/java/com/cerca/service/report/CsvService.java index 2b22daa..aa31b3f 100644 --- a/src/main/java/com/cerca/service/CsvService.java +++ b/src/main/java/com/cerca/service/report/CsvService.java @@ -1,4 +1,4 @@ -package com.cerca.service; +package com.cerca.service.report; import com.cerca.model.ReferenceItem; import java.io.BufferedWriter; diff --git a/src/main/java/com/cerca/service/ReportService.java b/src/main/java/com/cerca/service/report/ReportService.java similarity index 99% rename from src/main/java/com/cerca/service/ReportService.java rename to src/main/java/com/cerca/service/report/ReportService.java index dc7f43c..195cbfd 100644 --- a/src/main/java/com/cerca/service/ReportService.java +++ b/src/main/java/com/cerca/service/report/ReportService.java @@ -1,4 +1,4 @@ -package com.cerca.service; +package com.cerca.service.report; import com.cerca.model.ReferenceItem; import java.io.File; diff --git a/src/test/java/com/cerca/utils/PDFReadTest.java b/src/test/java/com/cerca/service/extraction/CermineServiceTest.java similarity index 95% rename from src/test/java/com/cerca/utils/PDFReadTest.java rename to src/test/java/com/cerca/service/extraction/CermineServiceTest.java index a20c01c..914c8bf 100644 --- a/src/test/java/com/cerca/utils/PDFReadTest.java +++ b/src/test/java/com/cerca/service/extraction/CermineServiceTest.java @@ -1,6 +1,5 @@ -package com.cerca.utils; +package com.cerca.service.extraction; -import com.cerca.service.CermineService; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -9,7 +8,7 @@ import java.io.File; -public class PDFReadTest { +public class CermineServiceTest { private CermineService cermineService; From 965f2fd8df03ef05d4487ebe25659b5b4d155aca Mon Sep 17 00:00:00 2001 From: Franz Zieris Date: Mon, 9 Mar 2026 09:56:16 +0100 Subject: [PATCH 2/6] Introduce PdfExtractor interface --- .../java/com/cerca/controller/MainController.java | 9 +++++---- ...ermineService.java => CerminePdfExtractor.java} | 2 +- .../com/cerca/service/extraction/PdfExtractor.java | 10 ++++++++++ ...rviceTest.java => CerminePdfExtractorTest.java} | 14 +++++++------- 4 files changed, 23 insertions(+), 12 deletions(-) rename src/main/java/com/cerca/service/extraction/{CermineService.java => CerminePdfExtractor.java} (97%) create mode 100644 src/main/java/com/cerca/service/extraction/PdfExtractor.java rename src/test/java/com/cerca/service/extraction/{CermineServiceTest.java => CerminePdfExtractorTest.java} (77%) diff --git a/src/main/java/com/cerca/controller/MainController.java b/src/main/java/com/cerca/controller/MainController.java index 458b54d..ab3297a 100644 --- a/src/main/java/com/cerca/controller/MainController.java +++ b/src/main/java/com/cerca/controller/MainController.java @@ -11,8 +11,9 @@ import java.util.concurrent.CompletionException; import com.cerca.model.ReferenceItem; -import com.cerca.service.extraction.CermineService; +import com.cerca.service.extraction.CerminePdfExtractor; import com.cerca.service.ConfigService; +import com.cerca.service.extraction.PdfExtractor; import com.cerca.service.lookup.CrossrefService; import com.cerca.service.report.CsvService; import com.cerca.service.LogService; @@ -52,7 +53,7 @@ public class MainController { private final MainView view; private final ObservableList data; - private final CermineService cermineService; + private final PdfExtractor pdfExtractor; private final CrossrefService crossrefService; private final CsvService csvService; private final ReportService reportService; @@ -66,7 +67,7 @@ public class MainController { public MainController(MainView view) { this.view = view; this.data = FXCollections.observableArrayList(); - this.cermineService = new CermineService(); + this.pdfExtractor = new CerminePdfExtractor(); this.csvService = new CsvService(); this.reportService = new ReportService(); this.logService = new LogService(); @@ -135,7 +136,7 @@ private void processPdf(File file) { CompletableFuture.supplyAsync(() -> { try { - return cermineService.extractReferences(file); + return pdfExtractor.extractReferences(file); } catch (Exception e) { throw new CompletionException(e); diff --git a/src/main/java/com/cerca/service/extraction/CermineService.java b/src/main/java/com/cerca/service/extraction/CerminePdfExtractor.java similarity index 97% rename from src/main/java/com/cerca/service/extraction/CermineService.java rename to src/main/java/com/cerca/service/extraction/CerminePdfExtractor.java index 329ff1e..e502c96 100644 --- a/src/main/java/com/cerca/service/extraction/CermineService.java +++ b/src/main/java/com/cerca/service/extraction/CerminePdfExtractor.java @@ -23,7 +23,7 @@ * Privacy note: PDF files are processed locally. * @author Lidiany Cerqueira */ -public class CermineService { +public class CerminePdfExtractor implements PdfExtractor { public List extractReferences(File pdfFile) throws Exception { List results = new ArrayList<>(); diff --git a/src/main/java/com/cerca/service/extraction/PdfExtractor.java b/src/main/java/com/cerca/service/extraction/PdfExtractor.java new file mode 100644 index 0000000..ca11c72 --- /dev/null +++ b/src/main/java/com/cerca/service/extraction/PdfExtractor.java @@ -0,0 +1,10 @@ +package com.cerca.service.extraction; + +import com.cerca.model.ReferenceItem; + +import java.io.File; +import java.util.List; + +public interface PdfExtractor { + List extractReferences(File pdfFile) throws Exception; +} diff --git a/src/test/java/com/cerca/service/extraction/CermineServiceTest.java b/src/test/java/com/cerca/service/extraction/CerminePdfExtractorTest.java similarity index 77% rename from src/test/java/com/cerca/service/extraction/CermineServiceTest.java rename to src/test/java/com/cerca/service/extraction/CerminePdfExtractorTest.java index 914c8bf..6034231 100644 --- a/src/test/java/com/cerca/service/extraction/CermineServiceTest.java +++ b/src/test/java/com/cerca/service/extraction/CerminePdfExtractorTest.java @@ -8,13 +8,13 @@ import java.io.File; -public class CermineServiceTest { +public class CerminePdfExtractorTest { - private CermineService cermineService; + private CerminePdfExtractor cerminePdfExtractor; @BeforeEach public void initialize() { - cermineService = new CermineService(); + cerminePdfExtractor = new CerminePdfExtractor(); } @Test @@ -22,7 +22,7 @@ public void initialize() { public void testIEEEPDFRead() throws Exception { String filePath = "src/test/resources/pdfs/Dummy_PDF_IEEE_Format.pdf"; File file = new File(filePath); - Assertions.assertFalse(cermineService.extractReferences(file).isEmpty()); + Assertions.assertFalse(cerminePdfExtractor.extractReferences(file).isEmpty()); } @Test @@ -30,7 +30,7 @@ public void testIEEEPDFRead() throws Exception { public void testAPAPDFRead() throws Exception { String filePath = "src/test/resources/pdfs/Dummy_PDF_APA_Format.pdf"; File file = new File(filePath); - Assertions.assertFalse(cermineService.extractReferences(file).isEmpty()); + Assertions.assertFalse(cerminePdfExtractor.extractReferences(file).isEmpty()); } @Test @@ -38,7 +38,7 @@ public void testAPAPDFRead() throws Exception { public void testWhatExceptionIsThrown(){ File file = new File("src/test/resources/pdfs/nonExistentFile.pdf"); Assertions.assertThrows(java.io.FileNotFoundException.class, () -> { - cermineService.extractReferences(file); + cerminePdfExtractor.extractReferences(file); }); } @@ -47,7 +47,7 @@ public void testWhatExceptionIsThrown(){ public void testNonPDFFileThrowsException(){ File file = new File("src/test/resources/not_a_pdf.txt"); Assertions.assertThrows(AnalysisException.class, () -> { - cermineService.extractReferences(file); + cerminePdfExtractor.extractReferences(file); }); } } From b04fe2e110ee0da971528543699e41dd43d00ff3 Mon Sep 17 00:00:00 2001 From: Franz Zieris Date: Mon, 9 Mar 2026 10:03:18 +0100 Subject: [PATCH 3/6] Make ReferenceParser non-static --- .../java/com/cerca/controller/MainController.java | 6 ++++-- .../extraction}/ReferenceParser.java | 12 ++++++------ .../extraction}/ReferenceParserTest.java | 8 +++++--- 3 files changed, 15 insertions(+), 11 deletions(-) rename src/main/java/com/cerca/{utils => service/extraction}/ReferenceParser.java (89%) rename src/test/java/com/cerca/{utils => service/extraction}/ReferenceParserTest.java (91%) diff --git a/src/main/java/com/cerca/controller/MainController.java b/src/main/java/com/cerca/controller/MainController.java index ab3297a..cccfec6 100644 --- a/src/main/java/com/cerca/controller/MainController.java +++ b/src/main/java/com/cerca/controller/MainController.java @@ -21,7 +21,7 @@ import com.cerca.service.report.ReportService; import com.cerca.service.lookup.SemanticScholarService; import com.cerca.service.lookup.ZenodoService; -import com.cerca.utils.ReferenceParser; +import com.cerca.service.extraction.ReferenceParser; import com.cerca.view.MainView; import javafx.application.Platform; @@ -54,6 +54,7 @@ public class MainController { private final MainView view; private final ObservableList data; private final PdfExtractor pdfExtractor; + private final ReferenceParser textExtractor; private final CrossrefService crossrefService; private final CsvService csvService; private final ReportService reportService; @@ -68,6 +69,7 @@ public MainController(MainView view) { this.view = view; this.data = FXCollections.observableArrayList(); this.pdfExtractor = new CerminePdfExtractor(); + this.textExtractor = new ReferenceParser(); this.csvService = new CsvService(); this.reportService = new ReportService(); this.logService = new LogService(); @@ -416,7 +418,7 @@ private void loadManualReferences(String text) { if (line.trim().length() < 5) continue; - ReferenceParser.ParsedData parsedData = ReferenceParser.parse(line); + ReferenceParser.ParsedData parsedData = textExtractor.parse(line); ReferenceItem item = new ReferenceItem(idCounter++, "WAITING", parsedData.authors, parsedData.title, line, ""); diff --git a/src/main/java/com/cerca/utils/ReferenceParser.java b/src/main/java/com/cerca/service/extraction/ReferenceParser.java similarity index 89% rename from src/main/java/com/cerca/utils/ReferenceParser.java rename to src/main/java/com/cerca/service/extraction/ReferenceParser.java index bb65158..957854d 100644 --- a/src/main/java/com/cerca/utils/ReferenceParser.java +++ b/src/main/java/com/cerca/service/extraction/ReferenceParser.java @@ -1,7 +1,7 @@ /** * @author Lidiany Cerqueira */ -package com.cerca.utils; +package com.cerca.service.extraction; import pl.edu.icm.cermine.bibref.CRFBibReferenceParser; import pl.edu.icm.cermine.bibref.model.BibEntry; @@ -12,14 +12,14 @@ public class ReferenceParser { - private static CRFBibReferenceParser parser; + private CRFBibReferenceParser parser; - static { + public ReferenceParser() { try { - parser = CRFBibReferenceParser.getInstance(); + this.parser = CRFBibReferenceParser.getInstance(); } catch (AnalysisException e) { System.err.println("CERMINE Model Error: " + e.getMessage()); - } + }; } public static class ParsedData { @@ -32,7 +32,7 @@ public ParsedData(String authors, String title) { } } - public static ParsedData parse(String rawReference) { + public ParsedData parse(String rawReference) { if (rawReference == null || rawReference.trim().isEmpty()) { return new ParsedData("Unknown", "Unknown"); } diff --git a/src/test/java/com/cerca/utils/ReferenceParserTest.java b/src/test/java/com/cerca/service/extraction/ReferenceParserTest.java similarity index 91% rename from src/test/java/com/cerca/utils/ReferenceParserTest.java rename to src/test/java/com/cerca/service/extraction/ReferenceParserTest.java index b2c87d1..214d97e 100644 --- a/src/test/java/com/cerca/utils/ReferenceParserTest.java +++ b/src/test/java/com/cerca/service/extraction/ReferenceParserTest.java @@ -1,4 +1,4 @@ -package com.cerca.utils; +package com.cerca.service.extraction; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -12,7 +12,8 @@ public class ReferenceParserTest { @ParameterizedTest @MethodSource("referenceProvider") void testAuthorExtraction(String input, String expectedAuthors) { - ReferenceParser.ParsedData result = ReferenceParser.parse(input); + ReferenceParser textParser = new ReferenceParser(); + ReferenceParser.ParsedData result = textParser.parse(input); assertEquals(expectedAuthors, result.authors); } @@ -20,7 +21,8 @@ void testAuthorExtraction(String input, String expectedAuthors) { @ParameterizedTest @MethodSource("referenceProvider") void testTitleExtraction(String input, String ignored, String expectedTitle) { - ReferenceParser.ParsedData result = ReferenceParser.parse(input); + ReferenceParser textParser = new ReferenceParser(); + ReferenceParser.ParsedData result = textParser.parse(input); assertEquals(expectedTitle, result.title); } From f257109299718ff9c33745e488841f5abf8c0fc8 Mon Sep 17 00:00:00 2001 From: Franz Zieris Date: Mon, 9 Mar 2026 10:08:24 +0100 Subject: [PATCH 4/6] Introduce TextExtractor interface --- .../java/com/cerca/controller/MainController.java | 12 +++++------- ...renceParser.java => CermineTextExtractor.java} | 15 +++------------ .../com/cerca/service/extraction/ParsedData.java | 4 ++++ .../cerca/service/extraction/TextExtractor.java | 5 +++++ ...serTest.java => CermineTextExtractorTest.java} | 14 +++++++------- 5 files changed, 24 insertions(+), 26 deletions(-) rename src/main/java/com/cerca/service/extraction/{ReferenceParser.java => CermineTextExtractor.java} (85%) create mode 100644 src/main/java/com/cerca/service/extraction/ParsedData.java create mode 100644 src/main/java/com/cerca/service/extraction/TextExtractor.java rename src/test/java/com/cerca/service/extraction/{ReferenceParserTest.java => CermineTextExtractorTest.java} (89%) diff --git a/src/main/java/com/cerca/controller/MainController.java b/src/main/java/com/cerca/controller/MainController.java index cccfec6..a298f21 100644 --- a/src/main/java/com/cerca/controller/MainController.java +++ b/src/main/java/com/cerca/controller/MainController.java @@ -11,9 +11,8 @@ import java.util.concurrent.CompletionException; import com.cerca.model.ReferenceItem; -import com.cerca.service.extraction.CerminePdfExtractor; +import com.cerca.service.extraction.*; import com.cerca.service.ConfigService; -import com.cerca.service.extraction.PdfExtractor; import com.cerca.service.lookup.CrossrefService; import com.cerca.service.report.CsvService; import com.cerca.service.LogService; @@ -21,7 +20,6 @@ import com.cerca.service.report.ReportService; import com.cerca.service.lookup.SemanticScholarService; import com.cerca.service.lookup.ZenodoService; -import com.cerca.service.extraction.ReferenceParser; import com.cerca.view.MainView; import javafx.application.Platform; @@ -54,7 +52,7 @@ public class MainController { private final MainView view; private final ObservableList data; private final PdfExtractor pdfExtractor; - private final ReferenceParser textExtractor; + private final TextExtractor textExtractor; private final CrossrefService crossrefService; private final CsvService csvService; private final ReportService reportService; @@ -69,7 +67,7 @@ public MainController(MainView view) { this.view = view; this.data = FXCollections.observableArrayList(); this.pdfExtractor = new CerminePdfExtractor(); - this.textExtractor = new ReferenceParser(); + this.textExtractor = new CermineTextExtractor(); this.csvService = new CsvService(); this.reportService = new ReportService(); this.logService = new LogService(); @@ -418,9 +416,9 @@ private void loadManualReferences(String text) { if (line.trim().length() < 5) continue; - ReferenceParser.ParsedData parsedData = textExtractor.parse(line); + ParsedData parsedData = textExtractor.parse(line); - ReferenceItem item = new ReferenceItem(idCounter++, "WAITING", parsedData.authors, parsedData.title, line, + ReferenceItem item = new ReferenceItem(idCounter++, "WAITING", parsedData.authors(), parsedData.title(), line, ""); data.add(item); diff --git a/src/main/java/com/cerca/service/extraction/ReferenceParser.java b/src/main/java/com/cerca/service/extraction/CermineTextExtractor.java similarity index 85% rename from src/main/java/com/cerca/service/extraction/ReferenceParser.java rename to src/main/java/com/cerca/service/extraction/CermineTextExtractor.java index 957854d..17726b0 100644 --- a/src/main/java/com/cerca/service/extraction/ReferenceParser.java +++ b/src/main/java/com/cerca/service/extraction/CermineTextExtractor.java @@ -10,11 +10,11 @@ import java.util.List; -public class ReferenceParser { +public class CermineTextExtractor implements TextExtractor { private CRFBibReferenceParser parser; - public ReferenceParser() { + public CermineTextExtractor() { try { this.parser = CRFBibReferenceParser.getInstance(); } catch (AnalysisException e) { @@ -22,16 +22,7 @@ public ReferenceParser() { }; } - public static class ParsedData { - public String authors; - public String title; - - public ParsedData(String authors, String title) { - this.authors = authors; - this.title = title; - } - } - + @Override public ParsedData parse(String rawReference) { if (rawReference == null || rawReference.trim().isEmpty()) { return new ParsedData("Unknown", "Unknown"); diff --git a/src/main/java/com/cerca/service/extraction/ParsedData.java b/src/main/java/com/cerca/service/extraction/ParsedData.java new file mode 100644 index 0000000..346db03 --- /dev/null +++ b/src/main/java/com/cerca/service/extraction/ParsedData.java @@ -0,0 +1,4 @@ +package com.cerca.service.extraction; + +public record ParsedData (String authors, String title) { +} diff --git a/src/main/java/com/cerca/service/extraction/TextExtractor.java b/src/main/java/com/cerca/service/extraction/TextExtractor.java new file mode 100644 index 0000000..6b720ca --- /dev/null +++ b/src/main/java/com/cerca/service/extraction/TextExtractor.java @@ -0,0 +1,5 @@ +package com.cerca.service.extraction; + +public interface TextExtractor { + ParsedData parse(String rawReference); +} diff --git a/src/test/java/com/cerca/service/extraction/ReferenceParserTest.java b/src/test/java/com/cerca/service/extraction/CermineTextExtractorTest.java similarity index 89% rename from src/test/java/com/cerca/service/extraction/ReferenceParserTest.java rename to src/test/java/com/cerca/service/extraction/CermineTextExtractorTest.java index 214d97e..f0b7d7e 100644 --- a/src/test/java/com/cerca/service/extraction/ReferenceParserTest.java +++ b/src/test/java/com/cerca/service/extraction/CermineTextExtractorTest.java @@ -8,23 +8,23 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -public class ReferenceParserTest { +public class CermineTextExtractorTest { @ParameterizedTest @MethodSource("referenceProvider") void testAuthorExtraction(String input, String expectedAuthors) { - ReferenceParser textParser = new ReferenceParser(); - ReferenceParser.ParsedData result = textParser.parse(input); + CermineTextExtractor textParser = new CermineTextExtractor(); + ParsedData result = textParser.parse(input); - assertEquals(expectedAuthors, result.authors); + assertEquals(expectedAuthors, result.authors()); } @ParameterizedTest @MethodSource("referenceProvider") void testTitleExtraction(String input, String ignored, String expectedTitle) { - ReferenceParser textParser = new ReferenceParser(); - ReferenceParser.ParsedData result = textParser.parse(input); + CermineTextExtractor textParser = new CermineTextExtractor(); + ParsedData result = textParser.parse(input); - assertEquals(expectedTitle, result.title); + assertEquals(expectedTitle, result.title()); } static Stream referenceProvider() { From 21182c642dafaf4485bd1bdac4198eeeb8b7a5d7 Mon Sep 17 00:00:00 2001 From: Franz Zieris Date: Mon, 9 Mar 2026 11:11:24 +0100 Subject: [PATCH 5/6] Introduce Guice for dependency injection --- pom.xml | 6 +++ src/main/java/com/cerca/App.java | 10 +++-- src/main/java/com/cerca/AppModule.java | 28 ++++++++++++ .../com/cerca/controller/MainController.java | 45 ++++++++++++------- .../java/com/cerca/service/ConfigService.java | 8 +++- .../java/com/cerca/service/LogService.java | 3 ++ .../cerca/service/lookup/CrossrefService.java | 35 +++++---------- .../cerca/service/lookup/OpenAlexService.java | 24 ++++------ .../lookup/SemanticScholarService.java | 32 +++++++------ .../cerca/service/lookup/ZenodoService.java | 4 ++ .../com/cerca/service/report/CsvService.java | 3 ++ .../cerca/service/report/ReportService.java | 3 ++ src/main/java/com/cerca/view/MainView.java | 2 + 13 files changed, 123 insertions(+), 80 deletions(-) create mode 100644 src/main/java/com/cerca/AppModule.java diff --git a/pom.xml b/pom.xml index 28d0cd9..b1760df 100644 --- a/pom.xml +++ b/pom.xml @@ -18,6 +18,12 @@ + + com.google.inject + guice + 7.0.0 + + pl.edu.icm cermine diff --git a/src/main/java/com/cerca/App.java b/src/main/java/com/cerca/App.java index 4ee993c..f9db8cd 100644 --- a/src/main/java/com/cerca/App.java +++ b/src/main/java/com/cerca/App.java @@ -2,6 +2,8 @@ import com.cerca.controller.MainController; import com.cerca.view.MainView; +import com.google.inject.Guice; +import com.google.inject.Injector; import javafx.application.Application; import javafx.scene.Scene; import javafx.stage.Stage; @@ -10,13 +12,13 @@ public class App extends Application { @Override public void start(Stage stage) { + Injector injector = Guice.createInjector(new AppModule()); + // Initialize MVC Components - MainView view = new MainView(); - new MainController(view); - + MainView view = injector.getInstance(MainView.class); + injector.getInstance(MainController.class); var iconStream = getClass().getResourceAsStream("/images/app_icon.png"); - if (iconStream != null) { Image icon = new Image(iconStream); diff --git a/src/main/java/com/cerca/AppModule.java b/src/main/java/com/cerca/AppModule.java new file mode 100644 index 0000000..78a9f0b --- /dev/null +++ b/src/main/java/com/cerca/AppModule.java @@ -0,0 +1,28 @@ +package com.cerca; + +import com.cerca.service.ConfigService; +import com.cerca.service.extraction.CerminePdfExtractor; +import com.cerca.service.extraction.CermineTextExtractor; +import com.cerca.service.extraction.PdfExtractor; +import com.cerca.service.extraction.TextExtractor; +import com.google.inject.AbstractModule; +import com.google.inject.Provides; +import com.google.inject.Singleton; +import com.google.inject.name.Named; + +/** + * Dependency Injection Container + */ +public class AppModule extends AbstractModule { + @Override + protected void configure() { + bind(PdfExtractor.class).to(CerminePdfExtractor.class).in(Singleton.class); + bind(TextExtractor.class).to(CermineTextExtractor.class).in(Singleton.class); + } + + @Provides + @Named("USER_EMAIL") + public String provideUserEmail(ConfigService configService) { + return configService.getProperty("USER_EMAIL"); + } +} diff --git a/src/main/java/com/cerca/controller/MainController.java b/src/main/java/com/cerca/controller/MainController.java index a298f21..b236a22 100644 --- a/src/main/java/com/cerca/controller/MainController.java +++ b/src/main/java/com/cerca/controller/MainController.java @@ -22,6 +22,8 @@ import com.cerca.service.lookup.ZenodoService; import com.cerca.view.MainView; +import com.google.inject.Inject; +import com.google.inject.Singleton; import javafx.application.Platform; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -47,6 +49,7 @@ * @author Lidiany Cerqueira * */ +@Singleton public class MainController { private final MainView view; @@ -63,23 +66,32 @@ public class MainController { private final OpenAlexService openAlexService; private final ConfigService configService; - public MainController(MainView view) { + @Inject + public MainController(MainView view, + LogService logService, + ConfigService configService, + PdfExtractor pdfExtractor, + TextExtractor textExtractor, + CrossrefService crossrefService, + OpenAlexService openAlexService, + ZenodoService zenodoService, + SemanticScholarService semScholarService, + CsvService csvService, + ReportService reportService) { this.view = view; this.data = FXCollections.observableArrayList(); - this.pdfExtractor = new CerminePdfExtractor(); - this.textExtractor = new CermineTextExtractor(); - this.csvService = new CsvService(); - this.reportService = new ReportService(); - this.logService = new LogService(); - this.crossrefService = new CrossrefService(logService); - this.zenodoService = new ZenodoService(logService); - this.openAlexService = new OpenAlexService(logService); - - this.semScholarService = new SemanticScholarService(logService); - this.configService = new ConfigService(logService); - this.semScholarService.setApiKey(configService.getProperty("SEMANTIC_SCHOLAR_API_KEY")); - this.crossrefService.setEmail(configService.getProperty("USER_EMAIL")); - this.openAlexService.setEmail(configService.getProperty("USER_EMAIL")); + this.pdfExtractor = pdfExtractor; + this.textExtractor = textExtractor; + this.csvService = csvService; + this.reportService = reportService; + this.logService = logService; + this.crossrefService = crossrefService; + this.zenodoService = zenodoService; + this.openAlexService = openAlexService; + + this.semScholarService = semScholarService; + this.configService = configService; + view.getTable().setItems(data); setupDragAndDrop(); @@ -429,7 +441,7 @@ private void loadManualReferences(String text) { } public void openSettingsDialog() { - String currentKey = this.semScholarService.getApiKey(); + String currentKey = configService.getProperty("SEMANTIC_SCHOLAR_API_KEY"); Dialog dialog = new Dialog<>(); dialog.setTitle("CERCA Settings"); @@ -488,7 +500,6 @@ public void openSettingsDialog() { result.ifPresent(newKey -> { configService.setProperty("SEMANTIC_SCHOLAR_API_KEY", newKey); - this.semScholarService.setApiKey(newKey); logService.log("INFO", "API Key updated successfully!"); }); } diff --git a/src/main/java/com/cerca/service/ConfigService.java b/src/main/java/com/cerca/service/ConfigService.java index 13fe011..2f3c66b 100644 --- a/src/main/java/com/cerca/service/ConfigService.java +++ b/src/main/java/com/cerca/service/ConfigService.java @@ -1,6 +1,8 @@ package com.cerca.service; -import java.io.File; +import com.google.inject.Inject; +import com.google.inject.Singleton; + import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Files; @@ -8,12 +10,14 @@ import java.nio.file.Paths; import java.util.Properties; +@Singleton public class ConfigService { private static final Path CONFIG_FILE = Paths.get("config.properties"); private final LogService logger; private Properties properties; - + + @Inject public ConfigService(LogService logger) { this.logger = logger; this.properties = new Properties(); diff --git a/src/main/java/com/cerca/service/LogService.java b/src/main/java/com/cerca/service/LogService.java index 785fe42..6a3e732 100644 --- a/src/main/java/com/cerca/service/LogService.java +++ b/src/main/java/com/cerca/service/LogService.java @@ -1,6 +1,8 @@ package com.cerca.service; +import com.google.inject.Singleton; + import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -14,6 +16,7 @@ * * @author Lidiany Cerqueira */ +@Singleton public class LogService { private static final Path LOG_FILE = Paths.get("cerca_audit.log"); diff --git a/src/main/java/com/cerca/service/lookup/CrossrefService.java b/src/main/java/com/cerca/service/lookup/CrossrefService.java index 207b069..4e70d67 100644 --- a/src/main/java/com/cerca/service/lookup/CrossrefService.java +++ b/src/main/java/com/cerca/service/lookup/CrossrefService.java @@ -12,6 +12,9 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import com.google.inject.name.Named; import javafx.scene.paint.Color; import me.xdrop.fuzzywuzzy.FuzzySearch; @@ -24,16 +27,18 @@ * The results returned by this service are used for comparison and verification. * @author Lidiany Cerqueira */ +@Singleton public class CrossrefService { private final LogService logger; private final HttpClient client; - private String email; + private final String email; - - - public CrossrefService(LogService logger) { + + @Inject + public CrossrefService(LogService logger, @Named("USER_EMAIL") String email) { this.logger = logger; + this.email = email; this.client = HttpClient.newBuilder().version(HttpClient.Version.HTTP_2).build(); } @@ -180,27 +185,9 @@ private void score(ReferenceItem item, String crTitle, String crAuthors, String /** Helper to safely format text for a URL**/ private String cleanText(String text) { if (text == null) return ""; - - - String clean = text.replaceAll("[^a-zA-Z0-9\\s]", ""); - - - return clean.trim().replace(" ", "+"); - } - /** - * @return the email - */ - public String getEmail() { - return email; - } + String clean = text.replaceAll("[^a-zA-Z0-9\\s]", ""); - /** - * @param email the email to set - */ - public void setEmail(String email) { - this.email = email; + return clean.trim().replace(" ", "+"); } - - } \ No newline at end of file diff --git a/src/main/java/com/cerca/service/lookup/OpenAlexService.java b/src/main/java/com/cerca/service/lookup/OpenAlexService.java index b5f9a00..9d24148 100644 --- a/src/main/java/com/cerca/service/lookup/OpenAlexService.java +++ b/src/main/java/com/cerca/service/lookup/OpenAlexService.java @@ -5,6 +5,9 @@ import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import com.google.inject.name.Named; import javafx.application.Platform; import javafx.scene.paint.Color; import me.xdrop.fuzzywuzzy.FuzzySearch; @@ -17,18 +20,21 @@ import java.nio.charset.StandardCharsets; import java.time.Duration; +@Singleton public class OpenAlexService { private final HttpClient client; private final LogService logger; - private String email; + private final String email; private static final String API_URL = "https://api.openalex.org/works"; - public OpenAlexService(LogService logger) { + @Inject + public OpenAlexService(LogService logger, @Named("USER_EMAIL") String email) { this.logger = logger; + this.email = email; this.client = HttpClient.newBuilder() .followRedirects(HttpClient.Redirect.NORMAL) .connectTimeout(Duration.ofSeconds(10)) @@ -164,18 +170,4 @@ private boolean parseAndScore(String json, ReferenceItem item) { } return false; } - - /** - * @return the email - */ - public String getEmail() { - return email; - } - - /** - * @param email the email to set - */ - public void setEmail(String email) { - this.email = email; - } } \ No newline at end of file diff --git a/src/main/java/com/cerca/service/lookup/SemanticScholarService.java b/src/main/java/com/cerca/service/lookup/SemanticScholarService.java index 0ca18dc..d9ddf6a 100644 --- a/src/main/java/com/cerca/service/lookup/SemanticScholarService.java +++ b/src/main/java/com/cerca/service/lookup/SemanticScholarService.java @@ -9,11 +9,15 @@ import java.time.Duration; import com.cerca.model.ReferenceItem; +import com.cerca.service.ConfigService; import com.cerca.service.LogService; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import com.google.inject.name.Named; import javafx.application.Platform; import javafx.scene.paint.Color; import me.xdrop.fuzzywuzzy.FuzzySearch; @@ -23,17 +27,20 @@ * if it exists. * * @author Lidiany Cerqueira */ +@Singleton public class SemanticScholarService { - private final HttpClient client; private final LogService logger; - private String apiKey = ""; // Stores the API key + private final ConfigService configService; + private final HttpClient client; // Semantic Scholar API Endpoint private static final String API_URL = "https://api.semanticscholar.org/graph/v1/paper/search"; - public SemanticScholarService(LogService logger) { + @Inject + public SemanticScholarService(LogService logger, ConfigService configService) { this.logger = logger; + this.configService = configService; this.client = HttpClient.newBuilder() .followRedirects(HttpClient.Redirect.NORMAL) .connectTimeout(Duration.ofSeconds(10)) @@ -42,8 +49,7 @@ public SemanticScholarService(LogService logger) { public boolean verify(ReferenceItem item) { - - if(apiKey.equals("")) { + if(getApiKey().equals("")) { logger.log("ERROR", "Set your SemanticScholar API key."); return false; } @@ -79,8 +85,8 @@ public boolean verify(ReferenceItem item) { .GET(); // Add the header if the key was loaded successfully - if (!apiKey.isEmpty()) { - requestBuilder.header("x-api-key", apiKey); + if (!getApiKey().isEmpty()) { + requestBuilder.header("x-api-key", getApiKey()); } HttpRequest request = requestBuilder.build(); @@ -199,16 +205,8 @@ private boolean parseAndScore(String json, ReferenceItem item) { /** * @return */ - public String getApiKey() { - // TODO Auto-generated method stub - return apiKey; + private String getApiKey() { + return configService.getProperty("SEMANTIC_SCHOLAR_API_KEY"); } - /** - * @param newKey - */ - public void setApiKey(String newKey) { - this.apiKey = newKey; - - } } \ No newline at end of file diff --git a/src/main/java/com/cerca/service/lookup/ZenodoService.java b/src/main/java/com/cerca/service/lookup/ZenodoService.java index 01033e0..aee0891 100644 --- a/src/main/java/com/cerca/service/lookup/ZenodoService.java +++ b/src/main/java/com/cerca/service/lookup/ZenodoService.java @@ -5,6 +5,8 @@ import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import com.google.inject.Inject; +import com.google.inject.Singleton; import javafx.application.Platform; import javafx.scene.paint.Color; import me.xdrop.fuzzywuzzy.FuzzySearch; @@ -26,11 +28,13 @@ * The results returned by this service are used for comparison and verification. * @author Lidiany Cerqueira */ +@Singleton public class ZenodoService { private final HttpClient client; private final LogService logger; + @Inject public ZenodoService(LogService logger) { this.logger = logger; this.client = HttpClient.newBuilder() diff --git a/src/main/java/com/cerca/service/report/CsvService.java b/src/main/java/com/cerca/service/report/CsvService.java index aa31b3f..85ee1be 100644 --- a/src/main/java/com/cerca/service/report/CsvService.java +++ b/src/main/java/com/cerca/service/report/CsvService.java @@ -1,6 +1,8 @@ package com.cerca.service.report; import com.cerca.model.ReferenceItem; +import com.google.inject.Singleton; + import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; @@ -12,6 +14,7 @@ * * @author Lidiany Cerqueira */ +@Singleton public class CsvService { public void exportToCsv(List data, File file) throws IOException { diff --git a/src/main/java/com/cerca/service/report/ReportService.java b/src/main/java/com/cerca/service/report/ReportService.java index 195cbfd..7fca0d3 100644 --- a/src/main/java/com/cerca/service/report/ReportService.java +++ b/src/main/java/com/cerca/service/report/ReportService.java @@ -1,6 +1,8 @@ package com.cerca.service.report; import com.cerca.model.ReferenceItem; +import com.google.inject.Singleton; + import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -13,6 +15,7 @@ * * @author Lidiany Cerqueira */ +@Singleton public class ReportService { public void exportReport(List items, File file) throws IOException { diff --git a/src/main/java/com/cerca/view/MainView.java b/src/main/java/com/cerca/view/MainView.java index 4843d1b..a94dc13 100644 --- a/src/main/java/com/cerca/view/MainView.java +++ b/src/main/java/com/cerca/view/MainView.java @@ -7,6 +7,7 @@ import com.cerca.model.ReferenceItem; +import com.google.inject.Singleton; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Parent; @@ -50,6 +51,7 @@ * * @author Lidiany Cerqueira */ +@Singleton public class MainView { private final BorderPane layout; From 768ecb33f3386c48af0556529090ad6a735091bb Mon Sep 17 00:00:00 2001 From: Franz Zieris Date: Mon, 9 Mar 2026 11:21:29 +0100 Subject: [PATCH 6/6] WIP: Use GROBID for text extraction --- src/main/java/com/cerca/AppModule.java | 7 +- .../extraction/GrobidTextExtractor.java | 96 +++++++++++++++++++ .../extraction/CermineTextExtractorTest.java | 2 +- .../extraction/GrobidTextExtractorTest.java | 64 +++++++++++++ 4 files changed, 163 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/cerca/service/extraction/GrobidTextExtractor.java create mode 100644 src/test/java/com/cerca/service/extraction/GrobidTextExtractorTest.java diff --git a/src/main/java/com/cerca/AppModule.java b/src/main/java/com/cerca/AppModule.java index 78a9f0b..0dc957e 100644 --- a/src/main/java/com/cerca/AppModule.java +++ b/src/main/java/com/cerca/AppModule.java @@ -1,10 +1,7 @@ package com.cerca; import com.cerca.service.ConfigService; -import com.cerca.service.extraction.CerminePdfExtractor; -import com.cerca.service.extraction.CermineTextExtractor; -import com.cerca.service.extraction.PdfExtractor; -import com.cerca.service.extraction.TextExtractor; +import com.cerca.service.extraction.*; import com.google.inject.AbstractModule; import com.google.inject.Provides; import com.google.inject.Singleton; @@ -17,7 +14,7 @@ public class AppModule extends AbstractModule { @Override protected void configure() { bind(PdfExtractor.class).to(CerminePdfExtractor.class).in(Singleton.class); - bind(TextExtractor.class).to(CermineTextExtractor.class).in(Singleton.class); + bind(TextExtractor.class).to(GrobidTextExtractor.class).in(Singleton.class); } @Provides diff --git a/src/main/java/com/cerca/service/extraction/GrobidTextExtractor.java b/src/main/java/com/cerca/service/extraction/GrobidTextExtractor.java new file mode 100644 index 0000000..2d7912b --- /dev/null +++ b/src/main/java/com/cerca/service/extraction/GrobidTextExtractor.java @@ -0,0 +1,96 @@ +package com.cerca.service.extraction; + +import org.w3c.dom.Document; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathFactory; +import java.io.IOException; +import java.io.StringReader; +import java.net.URI; +import java.net.URLEncoder; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +/** + * Call GROBID to extract a reference from a citation. + *

+ * To run GROBID in a Docker container, call + * docker run --rm -p 8070:8070 grobid/grobid:0.8.2-full. + */ +public class GrobidTextExtractor implements TextExtractor { + private static final String GROBID_URL = "http://localhost:8070"; + + private final HttpClient httpClient; + + public GrobidTextExtractor() { + this.httpClient = HttpClient.newHttpClient(); + } + + @Override + public ParsedData parse(String rawReference) { + try { + String responseXml = callGrobid(rawReference); + return parseTei(responseXml, rawReference); + } catch (Exception e) { + throw new RuntimeException("Failed to parse reference with GROBID", e); + } + } + + private String callGrobid(String citation) throws IOException, InterruptedException { + String body = "citations=" + URLEncoder.encode(citation, StandardCharsets.UTF_8); + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(GROBID_URL + "/api/processCitation")) + .header("Content-Type", "application/x-www-form-urlencoded") + .POST(HttpRequest.BodyPublishers.ofString(body)) + .build(); + + HttpResponse response = + httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + + if (response.statusCode() != 200) { + throw new RuntimeException("GROBID returned status " + response.statusCode()); + } + + return response.body(); + } + + private ParsedData parseTei(String xml, String rawReference) throws Exception { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(false); + + Document doc = factory.newDocumentBuilder() + .parse(new InputSource(new StringReader(xml))); + + var xpath = XPathFactory.newInstance().newXPath(); + + // --- Authors --- + NodeList surnameNodes = (NodeList) xpath.evaluate("//author/persName/surname", doc, XPathConstants.NODESET); + + List authors = new ArrayList<>(); + for (int i = 0; i < surnameNodes.getLength(); i++) { + authors.add(surnameNodes.item(i).getTextContent().trim()); + } + + String authorsJoined = String.join(", ", authors); + + // --- Title (analytic preferred) --- + String title = xpath.evaluate("//analytic/title/text()", doc).trim(); + + if (title.isEmpty()) { + title = xpath.evaluate("//monogr/title/text()", doc).trim(); + } + + // --- DOI --- + String doi = xpath.evaluate("//idno[@type='DOI']/text()", doc).trim(); + + return new ParsedData(authorsJoined, title); + } +} diff --git a/src/test/java/com/cerca/service/extraction/CermineTextExtractorTest.java b/src/test/java/com/cerca/service/extraction/CermineTextExtractorTest.java index f0b7d7e..b9520d7 100644 --- a/src/test/java/com/cerca/service/extraction/CermineTextExtractorTest.java +++ b/src/test/java/com/cerca/service/extraction/CermineTextExtractorTest.java @@ -51,7 +51,7 @@ static Stream referenceProvider() { ), Arguments.of( "[39] Charity Majors, Liz Fong-Jones, and George Miranda. Observability engineering. \" O’Reilly Media, Inc.\", 2022.", - "Majors, Charity, Fong-Jones Liz, Miranda, George", + "Majors, Charity, Fong-Jones, Liz, Miranda, George", "Observability engineering" ), Arguments.of( diff --git a/src/test/java/com/cerca/service/extraction/GrobidTextExtractorTest.java b/src/test/java/com/cerca/service/extraction/GrobidTextExtractorTest.java new file mode 100644 index 0000000..3d6ae4b --- /dev/null +++ b/src/test/java/com/cerca/service/extraction/GrobidTextExtractorTest.java @@ -0,0 +1,64 @@ +package com.cerca.service.extraction; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class GrobidTextExtractorTest { + @ParameterizedTest + @MethodSource("referenceProvider") + void testAuthorExtraction(String input, String expectedAuthors) { + GrobidTextExtractor textParser = new GrobidTextExtractor(); + ParsedData result = textParser.parse(input); + + assertEquals(expectedAuthors, result.authors()); + } + + @ParameterizedTest + @MethodSource("referenceProvider") + void testTitleExtraction(String input, String ignored, String expectedTitle) { + GrobidTextExtractor textParser = new GrobidTextExtractor(); + ParsedData result = textParser.parse(input); + + assertEquals(expectedTitle, result.title()); + } + + static Stream referenceProvider() { + return Stream.of( + Arguments.of( + "[80] N. Rytilä, “Addressing end-to-end testing challenges with cypress,” 2025.", + "Rytilä", + "Addressing end-to-end testing challenges with cypress" + ), + Arguments.of( + "[100] T. Zhang, Y. Liu, J. Gao, L. P. Gao, and J. Cheng, “Deep learning paper,” Ieee Software, vol. 37, no. 4, pp. 67–74, 2020.", + "Zhang, Liu, Gao, Gao, Cheng", + "Deep learning paper" + ), + Arguments.of( + "[50] Martina Yvonne Feilzer. Doing mixed methods research pragmatically: Implications for the rediscovery of pragmatism as a research paradigm. Journal of mixed methods research, 4(1):6–16, 2010.", + "Feilzer", + "Doing mixed methods research pragmatically: Implications for the rediscovery of pragmatism as a research paradigm" + ), + Arguments.of( + "[20] B. Yetiştiren, I. Özsoy, M. Ayerdem, and E. Tüzün, “Evaluating the code quality of ai-assisted code generation tools: An empirical study on github copilot, amazon codewhisperer, and chatgpt,” arXiv preprint arXiv:2304.10778, 2023. [Online]. Available: https://arxiv.org/abs/2304.10778", + "Yetiştiren, Özsoy, Ayerdem, Tüzün", + "Evaluating the code quality of ai-assisted code generation tools: An empirical study on github copilot, amazon codewhisperer, and chatgpt" + ), + Arguments.of( + "[39] Charity Majors, Liz Fong-Jones, and George Miranda. Observability engineering. \" O’Reilly Media, Inc.\", 2022.", + "Majors, Fong-Jones, Miranda", + "Observability engineering" + ), + Arguments.of( + "[27] R. Cavalcante, L. Oliveira, and A. Santos, “Developers’ perceptions of ai programming assistants: A case study of copilot, chatgpt, and gemini,” in Proceedings of the 2025 International Conference on Software Maintenance and Evolution (ICSME). IEEE, 2025.", + "Cavalcante, Oliveira, Santos", + "Developers’ perceptions of ai programming assistants: A case study of copilot, chatgpt, and gemini" + ) + ); + } +}