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..0dc957e --- /dev/null +++ b/src/main/java/com/cerca/AppModule.java @@ -0,0 +1,25 @@ +package com.cerca; + +import com.cerca.service.ConfigService; +import com.cerca.service.extraction.*; +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(GrobidTextExtractor.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 336ac5b..b236a22 100644 --- a/src/main/java/com/cerca/controller/MainController.java +++ b/src/main/java/com/cerca/controller/MainController.java @@ -11,18 +11,19 @@ import java.util.concurrent.CompletionException; import com.cerca.model.ReferenceItem; -import com.cerca.service.CermineService; +import com.cerca.service.extraction.*; 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.utils.ReferenceParser; +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.view.MainView; +import com.google.inject.Inject; +import com.google.inject.Singleton; import javafx.application.Platform; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -48,11 +49,13 @@ * @author Lidiany Cerqueira * */ +@Singleton public class MainController { private final MainView view; private final ObservableList data; - private final CermineService cermineService; + private final PdfExtractor pdfExtractor; + private final TextExtractor textExtractor; private final CrossrefService crossrefService; private final CsvService csvService; private final ReportService reportService; @@ -63,22 +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.cermineService = new CermineService(); - 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(); @@ -135,7 +148,7 @@ private void processPdf(File file) { CompletableFuture.supplyAsync(() -> { try { - return cermineService.extractReferences(file); + return pdfExtractor.extractReferences(file); } catch (Exception e) { throw new CompletionException(e); @@ -415,9 +428,9 @@ private void loadManualReferences(String text) { if (line.trim().length() < 5) continue; - ReferenceParser.ParsedData parsedData = ReferenceParser.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); @@ -428,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"); @@ -487,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/CermineService.java b/src/main/java/com/cerca/service/extraction/CerminePdfExtractor.java similarity index 95% rename from src/main/java/com/cerca/service/CermineService.java rename to src/main/java/com/cerca/service/extraction/CerminePdfExtractor.java index 243eeb6..e502c96 100644 --- a/src/main/java/com/cerca/service/CermineService.java +++ b/src/main/java/com/cerca/service/extraction/CerminePdfExtractor.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; @@ -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/utils/ReferenceParser.java b/src/main/java/com/cerca/service/extraction/CermineTextExtractor.java similarity index 77% rename from src/main/java/com/cerca/utils/ReferenceParser.java rename to src/main/java/com/cerca/service/extraction/CermineTextExtractor.java index bb65158..17726b0 100644 --- a/src/main/java/com/cerca/utils/ReferenceParser.java +++ b/src/main/java/com/cerca/service/extraction/CermineTextExtractor.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; @@ -10,29 +10,20 @@ import java.util.List; -public class ReferenceParser { +public class CermineTextExtractor implements TextExtractor { - private static CRFBibReferenceParser parser; + private CRFBibReferenceParser parser; - static { + public CermineTextExtractor() { try { - parser = CRFBibReferenceParser.getInstance(); + this.parser = CRFBibReferenceParser.getInstance(); } catch (AnalysisException e) { System.err.println("CERMINE Model Error: " + e.getMessage()); - } - } - - public static class ParsedData { - public String authors; - public String title; - - public ParsedData(String authors, String title) { - this.authors = authors; - this.title = title; - } + }; } - public static ParsedData parse(String rawReference) { + @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/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/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/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/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/main/java/com/cerca/service/CrossrefService.java b/src/main/java/com/cerca/service/lookup/CrossrefService.java similarity index 95% rename from src/main/java/com/cerca/service/CrossrefService.java rename to src/main/java/com/cerca/service/lookup/CrossrefService.java index c713553..4e70d67 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,9 +8,13 @@ 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; +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; @@ -23,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(); } @@ -179,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/OpenAlexService.java b/src/main/java/com/cerca/service/lookup/OpenAlexService.java similarity index 93% rename from src/main/java/com/cerca/service/OpenAlexService.java rename to src/main/java/com/cerca/service/lookup/OpenAlexService.java index 03d3fbf..9d24148 100644 --- a/src/main/java/com/cerca/service/OpenAlexService.java +++ b/src/main/java/com/cerca/service/lookup/OpenAlexService.java @@ -1,9 +1,13 @@ -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; +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; @@ -16,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 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)) @@ -163,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/SemanticScholarService.java b/src/main/java/com/cerca/service/lookup/SemanticScholarService.java similarity index 91% rename from src/main/java/com/cerca/service/SemanticScholarService.java rename to src/main/java/com/cerca/service/lookup/SemanticScholarService.java index 9ca2a9e..d9ddf6a 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,10 +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; @@ -22,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)) @@ -41,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; } @@ -78,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(); @@ -198,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/ZenodoService.java b/src/main/java/com/cerca/service/lookup/ZenodoService.java similarity index 97% rename from src/main/java/com/cerca/service/ZenodoService.java rename to src/main/java/com/cerca/service/lookup/ZenodoService.java index 04cd03d..aee0891 100644 --- a/src/main/java/com/cerca/service/ZenodoService.java +++ b/src/main/java/com/cerca/service/lookup/ZenodoService.java @@ -1,9 +1,12 @@ -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; +import com.google.inject.Inject; +import com.google.inject.Singleton; import javafx.application.Platform; import javafx.scene.paint.Color; import me.xdrop.fuzzywuzzy.FuzzySearch; @@ -25,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/CsvService.java b/src/main/java/com/cerca/service/report/CsvService.java similarity index 96% rename from src/main/java/com/cerca/service/CsvService.java rename to src/main/java/com/cerca/service/report/CsvService.java index 2b22daa..85ee1be 100644 --- a/src/main/java/com/cerca/service/CsvService.java +++ b/src/main/java/com/cerca/service/report/CsvService.java @@ -1,6 +1,8 @@ -package com.cerca.service; +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/ReportService.java b/src/main/java/com/cerca/service/report/ReportService.java similarity index 98% rename from src/main/java/com/cerca/service/ReportService.java rename to src/main/java/com/cerca/service/report/ReportService.java index dc7f43c..7fca0d3 100644 --- a/src/main/java/com/cerca/service/ReportService.java +++ b/src/main/java/com/cerca/service/report/ReportService.java @@ -1,6 +1,8 @@ -package com.cerca.service; +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; diff --git a/src/test/java/com/cerca/utils/PDFReadTest.java b/src/test/java/com/cerca/service/extraction/CerminePdfExtractorTest.java similarity index 75% rename from src/test/java/com/cerca/utils/PDFReadTest.java rename to src/test/java/com/cerca/service/extraction/CerminePdfExtractorTest.java index a20c01c..6034231 100644 --- a/src/test/java/com/cerca/utils/PDFReadTest.java +++ b/src/test/java/com/cerca/service/extraction/CerminePdfExtractorTest.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,13 +8,13 @@ import java.io.File; -public class PDFReadTest { +public class CerminePdfExtractorTest { - private CermineService cermineService; + private CerminePdfExtractor cerminePdfExtractor; @BeforeEach public void initialize() { - cermineService = new CermineService(); + cerminePdfExtractor = new CerminePdfExtractor(); } @Test @@ -23,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 @@ -31,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 @@ -39,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); }); } @@ -48,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); }); } } diff --git a/src/test/java/com/cerca/utils/ReferenceParserTest.java b/src/test/java/com/cerca/service/extraction/CermineTextExtractorTest.java similarity index 85% rename from src/test/java/com/cerca/utils/ReferenceParserTest.java rename to src/test/java/com/cerca/service/extraction/CermineTextExtractorTest.java index b2c87d1..b9520d7 100644 --- a/src/test/java/com/cerca/utils/ReferenceParserTest.java +++ b/src/test/java/com/cerca/service/extraction/CermineTextExtractorTest.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; @@ -8,21 +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.ParsedData result = ReferenceParser.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.ParsedData result = ReferenceParser.parse(input); + CermineTextExtractor textParser = new CermineTextExtractor(); + ParsedData result = textParser.parse(input); - assertEquals(expectedTitle, result.title); + assertEquals(expectedTitle, result.title()); } static Stream referenceProvider() { @@ -49,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" + ) + ); + } +}