From 49b85b14cb14fc6bc49f57191f4dca1d863cee5f Mon Sep 17 00:00:00 2001 From: Aspasia Tsagkaropoulou Date: Fri, 9 May 2025 15:18:17 +0300 Subject: [PATCH 01/35] Create JournalAbbreviationValidator & check for wrong escape characters --- .../JournalAbbreviationValidator.java | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java diff --git a/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java b/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java new file mode 100644 index 00000000000..c8f9d7a75e3 --- /dev/null +++ b/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java @@ -0,0 +1,119 @@ +package org.jabref.logic.journals; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class JournalAbbreviationValidator { + + public static class ValidationResult { + private final boolean isValid; + private final String message; + private final ValidationType type; + private final String fullName; + private final String abbreviation; + private final int lineNumber; + private final String suggestion; + + public ValidationResult(boolean isValid, String message, ValidationType type, + String fullName, String abbreviation, int lineNumber) { + this(isValid, message, type, fullName, abbreviation, lineNumber, ""); + } + + public ValidationResult(boolean isValid, String message, ValidationType type, + String fullName, String abbreviation, int lineNumber, String suggestion) { + this.isValid = isValid; + this.message = message; + this.type = type; + this.fullName = fullName; + this.abbreviation = abbreviation; + this.lineNumber = lineNumber; + this.suggestion = suggestion; + } + + public boolean isValid() { + return isValid; + } + + public String getMessage() { + return message; + } + + public ValidationType getType() { + return type; + } + + public String getFullName() { + return fullName; + } + + public String getAbbreviation() { + return abbreviation; + } + + public int getLineNumber() { + return lineNumber; + } + + public String getSuggestion() { + return suggestion; + } + + @Override + public String toString() { + return type + " at line " + lineNumber + ": " + message + + (suggestion.isEmpty() ? "" : " Suggestion: " + suggestion) + + " [" + fullName + " -> " + abbreviation + "]"; + } + } + + public enum ValidationType { + ERROR, + WARNING + } + + // Updated pattern to include more valid escape sequences + private static final Pattern INVALID_ESCAPE_PATTERN = Pattern.compile("(? issues = new ArrayList<>(); + private final Map> fullNameToAbbrev = new HashMap<>(); + private final Map> abbrevToFullName = new HashMap<>(); + + /** + * Checks if the journal name or abbreviation contains wrong escape characters + */ + public ValidationResult checkWrongEscape(String fullName, String abbreviation, int lineNumber) { + List escapeIssues = new ArrayList<>(); + + // Check full name + Matcher fullNameMatcher = INVALID_ESCAPE_PATTERN.matcher(fullName); + if (fullNameMatcher.find()) { + escapeIssues.add(new ValidationResult(false, + String.format("Invalid escape sequence in full name at position %d", fullNameMatcher.start()), + ValidationType.ERROR, + fullName, + abbreviation, + lineNumber, + "Use valid escape sequences: \\\\, \\\", \\n, \\t, \\r, \\b, \\f, \\uXXXX")); + } + + // Check abbreviation + Matcher abbrevMatcher = INVALID_ESCAPE_PATTERN.matcher(abbreviation); + if (abbrevMatcher.find()) { + escapeIssues.add(new ValidationResult(false, + String.format("Invalid escape sequence in abbreviation at position %d", abbrevMatcher.start()), + ValidationType.ERROR, + fullName, + abbreviation, + lineNumber, + "Use valid escape sequences: \\\\, \\\", \\n, \\t, \\r, \\b, \\f, \\uXXXX")); + } + + return escapeIssues.isEmpty() ? + new ValidationResult(true, "", ValidationType.ERROR, fullName, abbreviation, lineNumber) : + escapeIssues.get(0); + } +} From a133c311bae8dad437c79a57c7532ac3c788ec45 Mon Sep 17 00:00:00 2001 From: Aspasia Tsagkaropoulou Date: Fri, 9 May 2025 15:36:47 +0300 Subject: [PATCH 02/35] Check for non-UTF8 characters --- .../JournalAbbreviationValidator.java | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java b/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java index c8f9d7a75e3..578dd0c474f 100644 --- a/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java +++ b/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java @@ -1,5 +1,6 @@ package org.jabref.logic.journals; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -77,7 +78,7 @@ public enum ValidationType { // Updated pattern to include more valid escape sequences private static final Pattern INVALID_ESCAPE_PATTERN = Pattern.compile("(? issues = new ArrayList<>(); private final Map> fullNameToAbbrev = new HashMap<>(); private final Map> abbrevToFullName = new HashMap<>(); @@ -116,4 +117,29 @@ public ValidationResult checkWrongEscape(String fullName, String abbreviation, i new ValidationResult(true, "", ValidationType.ERROR, fullName, abbreviation, lineNumber) : escapeIssues.get(0); } + + /** + * Checks if the journal name or abbreviation contains non-UTF8 characters + */ + public ValidationResult checkNonUtf8(String fullName, String abbreviation, int lineNumber) { + if (!isValidUtf8(fullName) || !isValidUtf8(abbreviation)) { + return new ValidationResult(false, + "Journal name or abbreviation contains invalid UTF-8 sequences", + ValidationType.ERROR, + fullName, + abbreviation, + lineNumber, + "Ensure all characters are valid UTF-8. Remove or replace any invalid characters."); + } + return new ValidationResult(true, "", ValidationType.ERROR, fullName, abbreviation, lineNumber); + } + + private boolean isValidUtf8(String str) { + try { + byte[] bytes = str.getBytes(StandardCharsets.UTF_8); + return new String(bytes, StandardCharsets.UTF_8).equals(str); + } catch (Exception e) { + return false; + } + } } From ec293db03065b961a95f389f2717f51e07bb9e42 Mon Sep 17 00:00:00 2001 From: Aspasia Tsagkaropoulou Date: Tue, 20 May 2025 19:53:08 +0300 Subject: [PATCH 03/35] Add check for same letter starting --- .../JournalAbbreviationValidator.java | 110 +++++++++++++++--- 1 file changed, 92 insertions(+), 18 deletions(-) diff --git a/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java b/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java index 578dd0c474f..7c9028bdec6 100644 --- a/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java +++ b/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java @@ -5,9 +5,12 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.apache.commons.lang3.tuple.Pair; + public class JournalAbbreviationValidator { public static class ValidationResult { @@ -20,12 +23,12 @@ public static class ValidationResult { private final String suggestion; public ValidationResult(boolean isValid, String message, ValidationType type, - String fullName, String abbreviation, int lineNumber) { + String fullName, String abbreviation, int lineNumber) { this(isValid, message, type, fullName, abbreviation, lineNumber, ""); } public ValidationResult(boolean isValid, String message, ValidationType type, - String fullName, String abbreviation, int lineNumber, String suggestion) { + String fullName, String abbreviation, int lineNumber, String suggestion) { this.isValid = isValid; this.message = message; this.type = type; @@ -66,11 +69,18 @@ public String getSuggestion() { @Override public String toString() { return type + " at line " + lineNumber + ": " + message + - (suggestion.isEmpty() ? "" : " Suggestion: " + suggestion) + - " [" + fullName + " -> " + abbreviation + "]"; + (suggestion.isEmpty() ? "" : " Suggestion: " + suggestion) + + " [" + fullName + " -> " + abbreviation + "]"; } } + private static final Set> ALLOWED_MISMATCHES = Set.of( + Pair.of("Polish Academy of Sciences", "Acta Phys. Polon."), + Pair.of("Jagellonian University", "Acta Phys. Polon."), + Pair.of("Universităţii din Timișoara", "An. Univ."), + Pair.of("Universităţii \"Ovidius\" Constanţa", "An. Ştiinţ.") + ); + public enum ValidationType { ERROR, WARNING @@ -93,29 +103,29 @@ public ValidationResult checkWrongEscape(String fullName, String abbreviation, i Matcher fullNameMatcher = INVALID_ESCAPE_PATTERN.matcher(fullName); if (fullNameMatcher.find()) { escapeIssues.add(new ValidationResult(false, - String.format("Invalid escape sequence in full name at position %d", fullNameMatcher.start()), - ValidationType.ERROR, - fullName, - abbreviation, - lineNumber, - "Use valid escape sequences: \\\\, \\\", \\n, \\t, \\r, \\b, \\f, \\uXXXX")); + String.format("Invalid escape sequence in full name at position %d", fullNameMatcher.start()), + ValidationType.ERROR, + fullName, + abbreviation, + lineNumber, + "Use valid escape sequences: \\\\, \\\", \\n, \\t, \\r, \\b, \\f, \\uXXXX")); } // Check abbreviation Matcher abbrevMatcher = INVALID_ESCAPE_PATTERN.matcher(abbreviation); if (abbrevMatcher.find()) { escapeIssues.add(new ValidationResult(false, - String.format("Invalid escape sequence in abbreviation at position %d", abbrevMatcher.start()), - ValidationType.ERROR, - fullName, - abbreviation, - lineNumber, - "Use valid escape sequences: \\\\, \\\", \\n, \\t, \\r, \\b, \\f, \\uXXXX")); + String.format("Invalid escape sequence in abbreviation at position %d", abbrevMatcher.start()), + ValidationType.ERROR, + fullName, + abbreviation, + lineNumber, + "Use valid escape sequences: \\\\, \\\", \\n, \\t, \\r, \\b, \\f, \\uXXXX")); } return escapeIssues.isEmpty() ? - new ValidationResult(true, "", ValidationType.ERROR, fullName, abbreviation, lineNumber) : - escapeIssues.get(0); + new ValidationResult(true, "", ValidationType.ERROR, fullName, abbreviation, lineNumber) : + escapeIssues.get(0); } /** @@ -142,4 +152,68 @@ private boolean isValidUtf8(String str) { return false; } } + + /** + * Checks if the abbreviation starts with the same letter as the full name + */ + public ValidationResult checkStartingLetters(String fullName, String abbreviation, int lineNumber) { + fullName = fullName.trim(); + abbreviation = abbreviation.trim(); + + if (isAllowedException(fullName, abbreviation)) { + return new ValidationResult( + true, + "Allowed abbreviation exception", + ValidationType.ERROR, + fullName, + abbreviation, + lineNumber + ); + } + + String fullFirst = getFirstSignificantWord(fullName); + String abbrFirst = getFirstSignificantWord(abbreviation); + + // Validate initials + if (!abbrFirst.isEmpty() && + !fullFirst.isEmpty() && + !abbrFirst.toLowerCase().startsWith(fullFirst.substring(0, 1).toLowerCase())) { + return new ValidationResult( + false, + "Abbreviation does not begin with same letter as full journal name", + ValidationType.ERROR, + fullName, + abbreviation, + lineNumber, + String.format("Should start with '%c' (from '%s')", + fullFirst.toLowerCase().charAt(0), + fullFirst) + ); + } + + return new ValidationResult( + true, + "", + ValidationType.ERROR, + fullName, + abbreviation, + lineNumber + ); + } + + private boolean isAllowedException(String fullName, String abbreviation) { + return ALLOWED_MISMATCHES.stream() + .anyMatch(pair -> + fullName.equals(pair.getKey()) && + abbreviation.startsWith(pair.getValue()) + ); + } + + private String getFirstSignificantWord(String s) { + if (s == null || s.trim().isEmpty()) { + return ""; + } + return s.trim().split("[\\s\\-–.,:;'\"]+")[0]; + } } + From 1f00b0f2fd6f050a80b9beb9c414e3eeb8b0ed25 Mon Sep 17 00:00:00 2001 From: Aspasia Tsagkaropoulou Date: Wed, 21 May 2025 01:38:36 +0300 Subject: [PATCH 04/35] Check if abbreviation is same as full text --- .../journals/JournalAbbreviationValidator.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java b/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java index 7c9028bdec6..fea1fac453c 100644 --- a/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java +++ b/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java @@ -215,5 +215,22 @@ private String getFirstSignificantWord(String s) { } return s.trim().split("[\\s\\-–.,:;'\"]+")[0]; } + + /** + * Checks if the abbreviation is the same as the full text + */ + public ValidationResult checkAbbreviationEqualsFullText(String fullName, String abbreviation, int lineNumber) { + if (fullName.equalsIgnoreCase(abbreviation) && fullName.trim().split("\\s+").length > 1) { + return new ValidationResult(false, + "Abbreviation is the same as the full text", + ValidationType.WARNING, + fullName, + abbreviation, + lineNumber, + "Consider using a shorter abbreviation to distinguish it from the full name"); + } + + return new ValidationResult(true, "", ValidationType.ERROR, fullName, abbreviation, lineNumber); + } } From 3304609c5cf2f04048318d1b34b6e1676b97e1ba Mon Sep 17 00:00:00 2001 From: Aspasia Tsagkaropoulou Date: Wed, 28 May 2025 01:04:14 +0300 Subject: [PATCH 05/35] Add Manag. validation check --- .../journals/JournalAbbreviationValidator.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java b/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java index fea1fac453c..1f1134ea584 100644 --- a/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java +++ b/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java @@ -232,5 +232,21 @@ public ValidationResult checkAbbreviationEqualsFullText(String fullName, String return new ValidationResult(true, "", ValidationType.ERROR, fullName, abbreviation, lineNumber); } + + /** + * Checks if the abbreviation uses outdated "Manage." instead of "Manag." + */ + public ValidationResult checkOutdatedManagementAbbreviation(String fullName, String abbreviation, int lineNumber) { + if (fullName.contains("Management") && abbreviation.contains("Manage.")) { + return new ValidationResult(false, + "Management is abbreviated with outdated \"Manage.\" instead of \"Manag.\"", + ValidationType.WARNING, + fullName, + abbreviation, + lineNumber, + "Update to use the standard abbreviation \"Manag.\""); + } + return new ValidationResult(true, "", ValidationType.WARNING, fullName, abbreviation, lineNumber); + } } From 8472ef9a71229a43649b1a27db306a3c4eb54b61 Mon Sep 17 00:00:00 2001 From: Aspasia Tsagkaropoulou Date: Wed, 28 May 2025 01:05:35 +0300 Subject: [PATCH 06/35] Add check for duplicate full names with different abbrev --- .../JournalAbbreviationValidator.java | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java b/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java index 1f1134ea584..85ce25c2090 100644 --- a/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java +++ b/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java @@ -248,5 +248,25 @@ public ValidationResult checkOutdatedManagementAbbreviation(String fullName, Str } return new ValidationResult(true, "", ValidationType.WARNING, fullName, abbreviation, lineNumber); } -} + /** + * Check for duplicate full names with different abbreviations + */ + private void checkDuplicateFullNames() { + for (Map.Entry> entry : fullNameToAbbrev.entrySet()) { + if (entry.getValue().size() > 1) { + String fullName = entry.getKey(); + List abbreviations = entry.getValue(); + + issues.add(new ValidationResult(false, + String.format("Duplicate full name '%s' with different abbreviations: %s", + fullName, String.join(", ", abbreviations)), + ValidationType.WARNING, + fullName, + abbreviations.get(0), + -1, + "Consider consolidating abbreviations or using more specific full names")); + } + } + } +} From 4a6790779867970a62cb6a24d88c4b28680f2155 Mon Sep 17 00:00:00 2001 From: Aspasia Tsagkaropoulou Date: Wed, 28 May 2025 01:13:34 +0300 Subject: [PATCH 07/35] Add check for duplicate abbreviations with different full names --- .../JournalAbbreviationValidator.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java b/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java index 85ce25c2090..b1fcc209bcc 100644 --- a/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java +++ b/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java @@ -269,4 +269,25 @@ private void checkDuplicateFullNames() { } } } + + /** + * Check for duplicate abbreviations with different full names + */ + private void checkDuplicateAbbreviations() { + for (Map.Entry> entry : abbrevToFullName.entrySet()) { + if (entry.getValue().size() > 1) { + String abbreviation = entry.getKey(); + List fullNames = entry.getValue(); + + issues.add(new ValidationResult(false, + String.format("Duplicate abbreviation '%s' used for different journals: %s", + abbreviation, String.join("; ", fullNames)), + ValidationType.WARNING, + fullNames.get(0), + abbreviation, + -1, + "Consider using more specific abbreviations to avoid ambiguity")); + } + } + } } From f8e75507c06e9dafe5ed12ed571d74e747ffd62f Mon Sep 17 00:00:00 2001 From: Aspasia Tsagkaropoulou Date: Wed, 28 May 2025 14:43:52 +0300 Subject: [PATCH 08/35] Implement journal entry validation logic --- .../JournalAbbreviationValidator.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java b/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java index b1fcc209bcc..e8f176be41a 100644 --- a/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java +++ b/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java @@ -290,4 +290,26 @@ private void checkDuplicateAbbreviations() { } } } + + /** + * Validates a journal entry against all rules + */ + public List validate(String fullName, String abbreviation, int lineNumber) { + List results = new ArrayList<>(); + + // Error checks + results.add(checkWrongEscape(fullName, abbreviation, lineNumber)); + results.add(checkNonUtf8(fullName, abbreviation, lineNumber)); + results.add(checkStartingLetters(fullName, abbreviation, lineNumber)); + + // Warning checks + results.add(checkAbbreviationEqualsFullText(fullName, abbreviation, lineNumber)); + results.add(checkOutdatedManagementAbbreviation(fullName, abbreviation, lineNumber)); + + // Track for duplicate checks + fullNameToAbbrev.computeIfAbsent(fullName, k -> new ArrayList<>()).add(abbreviation); + abbrevToFullName.computeIfAbsent(abbreviation, k -> new ArrayList<>()).add(fullName); + + return results; + } } From 2b8c6b2c4f9c4a808829e7e31dc1e09fbac5f6e5 Mon Sep 17 00:00:00 2001 From: Aspasia Tsagkaropoulou Date: Wed, 28 May 2025 14:49:27 +0300 Subject: [PATCH 09/35] Add issue retrieval method --- .../logic/journals/JournalAbbreviationValidator.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java b/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java index e8f176be41a..1f791d8543a 100644 --- a/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java +++ b/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java @@ -312,4 +312,14 @@ public List validate(String fullName, String abbreviation, int return results; } + + /** + * Get all validation issues found + */ + public List getIssues() { + // Check for duplicates + checkDuplicateFullNames(); + checkDuplicateAbbreviations(); + return issues; + } } From 366e776be93b8d655e2ae413034606977a6df1a8 Mon Sep 17 00:00:00 2001 From: Aspasia Tsagkaropoulou Date: Wed, 28 May 2025 15:10:19 +0300 Subject: [PATCH 10/35] Initialize JournalAbbreviationValidatorTest class --- .../journals/JournalAbbreviationValidatorTest.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java diff --git a/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java b/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java new file mode 100644 index 00000000000..2356725ff36 --- /dev/null +++ b/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java @@ -0,0 +1,13 @@ +package org.jabref.logic.journals; + +import org.junit.jupiter.api.BeforeEach; + +class JournalAbbreviationValidatorTest { + + private JournalAbbreviationValidator validator; + + @BeforeEach + void setUp() { + validator = new JournalAbbreviationValidator(); + } +} From 664d96e4ca364c60e856808209add31ba251075f Mon Sep 17 00:00:00 2001 From: Aspasia Tsagkaropoulou Date: Wed, 28 May 2025 15:45:41 +0300 Subject: [PATCH 11/35] Add escape test cases --- .../JournalAbbreviationValidatorTest.java | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java b/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java index 2356725ff36..461f0b66544 100644 --- a/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java +++ b/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java @@ -1,6 +1,11 @@ package org.jabref.logic.journals; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; class JournalAbbreviationValidatorTest { @@ -10,4 +15,50 @@ class JournalAbbreviationValidatorTest { void setUp() { validator = new JournalAbbreviationValidator(); } + + @Test + void checkWrongEscapeWithInvalidFullName() { + String fullName = "Zeszyty Naukowe Wy\\"; + String abbreviation = "Problemy Mat."; + + var result = validator.checkWrongEscape(fullName, abbreviation, 1); + assertFalse(result.isValid()); + assertEquals("Invalid escape sequence in full name at position 15", result.getMessage()); + assertEquals("Use valid escape sequences: \\\\, \\\", \\n, \\t, \\r, \\b, \\f, \\uXXXX", result.getSuggestion()); + assertEquals(1, result.getLineNumber()); + } + + @Test + void checkWrongEscapeWithInvalidAbbreviation() { + String fullName = "Journal of Evolutionary Biochemistry and Physiology"; + String abbreviation = "J. Evol. Biochem. Physiol.\\"; + + var result = validator.checkWrongEscape(fullName, abbreviation, 2); + assertFalse(result.isValid()); + assertEquals("Invalid escape sequence in abbreviation at position 22", result.getMessage()); + assertEquals("Use valid escape sequences: \\\\, \\\", \\n, \\t, \\r, \\b, \\f, \\uXXXX", result.getSuggestion()); + assertEquals(2, result.getLineNumber()); + } + + @Test + void checkWrongEscapeWithValidEscapes() { + String fullName = "Journal with \\n newline and \\t tab"; + String abbreviation = "J. with \\r return and \\b backspace"; + + var result = validator.checkWrongEscape(fullName, abbreviation, 1); + assertTrue(result.isValid()); + assertEquals("", result.getMessage()); + assertEquals("", result.getSuggestion()); + } + + @Test + void checkWrongEscapeWithMultipleIssues() { + String fullName = "Journal with \\x invalid"; + String abbreviation = "J. with \\y invalid"; + + var result = validator.checkWrongEscape(fullName, abbreviation, 1); + assertFalse(result.isValid()); + assertTrue(result.getMessage().startsWith("Invalid escape sequence")); + assertTrue(result.getSuggestion().contains("valid escape sequences")); + } } From a45c1a6f7529a97042ab8a2f88c2509dda2ee77c Mon Sep 17 00:00:00 2001 From: Aspasia Tsagkaropoulou Date: Wed, 28 May 2025 15:46:53 +0300 Subject: [PATCH 12/35] Add UTF-8 test cases --- .../JournalAbbreviationValidatorTest.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java b/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java index 461f0b66544..a3278841493 100644 --- a/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java +++ b/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java @@ -61,4 +61,27 @@ void checkWrongEscapeWithMultipleIssues() { assertTrue(result.getMessage().startsWith("Invalid escape sequence")); assertTrue(result.getSuggestion().contains("valid escape sequences")); } + + @Test + void checkNonUtf8WithValidInput() { + String fullName = "Journal of Physics"; + String abbreviation = "J. Phys."; + + var result = validator.checkNonUtf8(fullName, abbreviation, 1); + assertTrue(result.isValid()); + assertEquals("", result.getMessage()); + assertEquals("", result.getSuggestion()); + } + + @Test + void checkNonUtf8WithInvalidInput() { + // Using a non-UTF8 character + String fullName = "Journal of Physics\uFFFD"; + String abbreviation = "J. Phys."; + + var result = validator.checkNonUtf8(fullName, abbreviation, 1); + assertFalse(result.isValid()); + assertEquals("Journal name or abbreviation contains invalid UTF-8 sequences", result.getMessage()); + assertEquals("Ensure all characters are valid UTF-8. Remove or replace any invalid characters.", result.getSuggestion()); + } } From 04581583817851349857400db3a8ea179603ff94 Mon Sep 17 00:00:00 2001 From: Aspasia Tsagkaropoulou Date: Wed, 28 May 2025 15:49:00 +0300 Subject: [PATCH 13/35] Add starting letters test cases --- .../JournalAbbreviationValidatorTest.java | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java b/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java index a3278841493..6078db4cfb1 100644 --- a/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java +++ b/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java @@ -84,4 +84,48 @@ void checkNonUtf8WithInvalidInput() { assertEquals("Journal name or abbreviation contains invalid UTF-8 sequences", result.getMessage()); assertEquals("Ensure all characters are valid UTF-8. Remove or replace any invalid characters.", result.getSuggestion()); } + + @Test + void checkStartingLettersWithValidInput() { + String fullName = "Journal of Physics"; + String abbreviation = "J. Phys."; + + var result = validator.checkStartingLetters(fullName, abbreviation, 1); + assertTrue(result.isValid()); + assertEquals("", result.getMessage()); + assertEquals("", result.getSuggestion()); + } + + @Test + void checkStartingLettersWithInvalidInput() { + String fullName = "Journal of Physics"; + String abbreviation = "Phys. J."; + + var result = validator.checkStartingLetters(fullName, abbreviation, 1); + assertFalse(result.isValid()); + assertEquals("Abbreviation does not begin with same letter as full journal name", result.getMessage()); + assertEquals("Should start with 'j' (from 'Journal')", result.getSuggestion()); + } + + @Test + void checkStartingLettersWithThePrefix() { + String fullName = "The Journal of Physics"; + String abbreviation = "J. Phys."; + + var result = validator.checkStartingLetters(fullName, abbreviation, 1); + assertTrue(result.isValid()); + assertEquals("", result.getMessage()); + assertEquals("", result.getSuggestion()); + } + + @Test + void checkStartingLettersWithAPrefix() { + String fullName = "A Journal of Physics"; + String abbreviation = "J. Phys."; + + var result = validator.checkStartingLetters(fullName, abbreviation, 1); + assertTrue(result.isValid()); + assertEquals("", result.getMessage()); + assertEquals("", result.getSuggestion()); + } } From 67c8a7a4e4ccedf59a3a0a7f6491fb5f83f39946 Mon Sep 17 00:00:00 2001 From: Aspasia Tsagkaropoulou Date: Wed, 28 May 2025 15:50:08 +0300 Subject: [PATCH 14/35] Add AbbreviationEqualsFullText test cases --- .../JournalAbbreviationValidatorTest.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java b/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java index 6078db4cfb1..07e067a1f28 100644 --- a/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java +++ b/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java @@ -128,4 +128,27 @@ void checkStartingLettersWithAPrefix() { assertEquals("", result.getMessage()); assertEquals("", result.getSuggestion()); } + + @Test + void checkAbbreviationEqualsFullTextWithValidInput() { + String fullName = "Journal of Physics"; + String abbreviation = "J. Phys."; + + var result = validator.checkAbbreviationEqualsFullText(fullName, abbreviation, 1); + assertTrue(result.isValid()); + assertEquals("", result.getMessage()); + assertEquals("", result.getSuggestion()); + } + + @Test + void checkAbbreviationEqualsFullTextWithInvalidInput() { + String fullName = "Quantum"; + String abbreviation = "Quantum"; + + var result = validator.checkAbbreviationEqualsFullText(fullName, abbreviation, 1); + assertFalse(result.isValid()); + assertEquals("Abbreviation is the same as the full text", result.getMessage()); + assertEquals("Consider using a shorter abbreviation to distinguish it from the full name", result.getSuggestion()); + } + } From 2d5e36bcd01c3ecc1b1d7b662384f60f0d597aa3 Mon Sep 17 00:00:00 2001 From: Aspasia Tsagkaropoulou Date: Wed, 28 May 2025 16:01:54 +0300 Subject: [PATCH 15/35] Add outdated manag. test cases --- .../JournalAbbreviationValidatorTest.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java b/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java index 07e067a1f28..7602b9c4ca0 100644 --- a/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java +++ b/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java @@ -150,5 +150,27 @@ void checkAbbreviationEqualsFullTextWithInvalidInput() { assertEquals("Abbreviation is the same as the full text", result.getMessage()); assertEquals("Consider using a shorter abbreviation to distinguish it from the full name", result.getSuggestion()); } + + @Test + void checkOutdatedManagementAbbreviationWithValidInput() { + String fullName = "Management Science"; + String abbreviation = "Manag. Sci."; + + var result = validator.checkOutdatedManagementAbbreviation(fullName, abbreviation, 1); + assertTrue(result.isValid()); + assertEquals("", result.getMessage()); + assertEquals("", result.getSuggestion()); + } + + @Test + void checkOutdatedManagementAbbreviationWithInvalidInput() { + String fullName = "Management Science"; + String abbreviation = "Manage. Sci."; + + var result = validator.checkOutdatedManagementAbbreviation(fullName, abbreviation, 1); + assertFalse(result.isValid()); + assertEquals("Management is abbreviated with outdated \"Manage.\" instead of \"Manag.\"", result.getMessage()); + assertEquals("Update to use the standard abbreviation \"Manag.\"", result.getSuggestion()); + } } From 6eac2af64540ea0c1729ceb7d38d2ddabc472904 Mon Sep 17 00:00:00 2001 From: Aspasia Tsagkaropoulou Date: Wed, 28 May 2025 16:04:30 +0300 Subject: [PATCH 16/35] Add duplicate test cases --- .../JournalAbbreviationValidatorTest.java | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java b/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java index 7602b9c4ca0..5661ce1575c 100644 --- a/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java +++ b/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java @@ -150,7 +150,7 @@ void checkAbbreviationEqualsFullTextWithInvalidInput() { assertEquals("Abbreviation is the same as the full text", result.getMessage()); assertEquals("Consider using a shorter abbreviation to distinguish it from the full name", result.getSuggestion()); } - + @Test void checkOutdatedManagementAbbreviationWithValidInput() { String fullName = "Management Science"; @@ -173,4 +173,33 @@ void checkOutdatedManagementAbbreviationWithInvalidInput() { assertEquals("Update to use the standard abbreviation \"Manag.\"", result.getSuggestion()); } + @Test + void checkDuplicateFullNames() { + // Add first entry + validator.validate("Journal of Physics", "J. Phys.", 1); + // Add duplicate with different abbreviation + validator.validate("Journal of Physics", "J. Phys. A", 2); + + var issues = validator.getIssues(); + assertFalse(issues.isEmpty()); + assertTrue(issues.stream() + .anyMatch(issue -> issue.getMessage().contains("Duplicate full name"))); + assertTrue(issues.stream() + .anyMatch(issue -> issue.getSuggestion().contains("consolidating abbreviations"))); + } + + @Test + void checkDuplicateAbbreviations() { + // Add first entry + validator.validate("Journal of Physics", "J. Phys.", 1); + // Add different journal with same abbreviation + validator.validate("Journal of Physiology", "J. Phys.", 2); + + var issues = validator.getIssues(); + assertFalse(issues.isEmpty()); + assertTrue(issues.stream() + .anyMatch(issue -> issue.getMessage().contains("Duplicate abbreviation"))); + assertTrue(issues.stream() + .anyMatch(issue -> issue.getSuggestion().contains("more specific abbreviations"))); + } } From 6c71f5d489db3b651fe78ad2c52d8e04615dd471 Mon Sep 17 00:00:00 2001 From: Aspasia Tsagkaropoulou Date: Wed, 28 May 2025 16:09:44 +0300 Subject: [PATCH 17/35] Add more escape test cases --- .../JournalAbbreviationValidatorTest.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java b/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java index 5661ce1575c..f71bc90cf24 100644 --- a/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java +++ b/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java @@ -202,4 +202,37 @@ void checkDuplicateAbbreviations() { assertTrue(issues.stream() .anyMatch(issue -> issue.getSuggestion().contains("more specific abbreviations"))); } + + @Test + void checkWrongEscapeWithMultipleValidEscapes() { + String fullName = "Journal with \\n\\t\\r\\b\\f\\\"\\\\"; + String abbreviation = "J. with \\n\\t\\r\\b\\f\\\"\\\\"; + + var result = validator.checkWrongEscape(fullName, abbreviation, 1); + assertTrue(result.isValid()); + assertEquals("", result.getMessage()); + assertEquals("", result.getSuggestion()); + } + + @Test + void checkWrongEscapeWithUnicodeEscape() { + String fullName = "Journal with \\u0041"; + String abbreviation = "J. with \\u0042"; + + var result = validator.checkWrongEscape(fullName, abbreviation, 1); + assertTrue(result.isValid()); + assertEquals("", result.getMessage()); + assertEquals("", result.getSuggestion()); + } + + @Test + void checkWrongEscapeWithInvalidUnicodeEscape() { + String fullName = "Journal with \\u004"; + String abbreviation = "J. Phys."; + + var result = validator.checkWrongEscape(fullName, abbreviation, 1); + assertFalse(result.isValid()); + assertEquals("Invalid escape sequence in full name at position 13", result.getMessage()); + assertTrue(result.getSuggestion().contains("valid escape sequences")); + } } From 5846c140a5782fece53d69bb2cbe809e86eb62b6 Mon Sep 17 00:00:00 2001 From: Aspasia Tsagkaropoulou Date: Wed, 28 May 2025 16:11:17 +0300 Subject: [PATCH 18/35] Add non UTF-8 test cases --- .../journals/JournalAbbreviationValidatorTest.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java b/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java index f71bc90cf24..a154695375b 100644 --- a/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java +++ b/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java @@ -235,4 +235,15 @@ void checkWrongEscapeWithInvalidUnicodeEscape() { assertEquals("Invalid escape sequence in full name at position 13", result.getMessage()); assertTrue(result.getSuggestion().contains("valid escape sequences")); } + + @Test + void checkNonUtf8WithMultipleInvalidCharacters() { + String fullName = "Journal of Physics\uFFFD\uFFFD"; + String abbreviation = "J. Phys.\uFFFD"; + + var result = validator.checkNonUtf8(fullName, abbreviation, 1); + assertFalse(result.isValid()); + assertEquals("Journal name or abbreviation contains invalid UTF-8 sequences", result.getMessage()); + assertTrue(result.getSuggestion().contains("valid UTF-8")); + } } From a67e487e2907c2aa62ffd8f28185d1252dd7b78d Mon Sep 17 00:00:00 2001 From: Aspasia Tsagkaropoulou Date: Fri, 30 May 2025 13:15:34 +0300 Subject: [PATCH 19/35] Add starting letters test cases --- .../JournalAbbreviationValidatorTest.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java b/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java index a154695375b..991700b1954 100644 --- a/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java +++ b/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java @@ -246,4 +246,37 @@ void checkNonUtf8WithMultipleInvalidCharacters() { assertEquals("Journal name or abbreviation contains invalid UTF-8 sequences", result.getMessage()); assertTrue(result.getSuggestion().contains("valid UTF-8")); } + + @Test + void checkStartingLettersWithMultiplePrefixes() { + String fullName = "The A An Journal of Physics"; + String abbreviation = "J. Phys."; + + var result = validator.checkStartingLetters(fullName, abbreviation, 1); + assertTrue(result.isValid()); + assertEquals("", result.getMessage()); + assertEquals("", result.getSuggestion()); + } + + @Test + void checkStartingLettersWithSpecialCharacters() { + String fullName = "The Journal of Physics & Chemistry"; + String abbreviation = "J. Phys. Chem."; + + var result = validator.checkStartingLetters(fullName, abbreviation, 1); + assertTrue(result.isValid()); + assertEquals("", result.getMessage()); + assertEquals("", result.getSuggestion()); + } + + @Test + void checkStartingLettersWithNumbers() { + String fullName = "2D Materials"; + String abbreviation = "2D Mater."; + + var result = validator.checkStartingLetters(fullName, abbreviation, 1); + assertTrue(result.isValid()); + assertEquals("", result.getMessage()); + assertEquals("", result.getSuggestion()); + } } From be0e86ac429d082c64bfb586a3524913044f1629 Mon Sep 17 00:00:00 2001 From: Aspasia Tsagkaropoulou Date: Fri, 30 May 2025 13:44:40 +0300 Subject: [PATCH 20/35] Add empty inputs & whitespaces only test cases --- .../journals/JournalAbbreviationValidatorTest.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java b/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java index 991700b1954..24656845cd9 100644 --- a/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java +++ b/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java @@ -279,4 +279,18 @@ void checkStartingLettersWithNumbers() { assertEquals("", result.getMessage()); assertEquals("", result.getSuggestion()); } + + @Test + void validateWithEmptyInputs() { + var results = validator.validate("", "", 1); + assertFalse(results.isEmpty()); + assertTrue(results.stream().anyMatch(r -> !r.isValid())); + } + + @Test + void validateWithWhitespaceOnly() { + var results = validator.validate(" ", " ", 1); + assertFalse(results.isEmpty()); + assertTrue(results.stream().anyMatch(r -> !r.isValid())); + } } From 22e6eacee19e0a1caa083764f1324d8ed58aba79 Mon Sep 17 00:00:00 2001 From: Aspasia Tsagkaropoulou Date: Fri, 30 May 2025 13:47:02 +0300 Subject: [PATCH 21/35] Add more duplicate check test cases --- .../JournalAbbreviationValidatorTest.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java b/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java index 24656845cd9..1ddbffc3c6b 100644 --- a/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java +++ b/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java @@ -293,4 +293,26 @@ void validateWithWhitespaceOnly() { assertFalse(results.isEmpty()); assertTrue(results.stream().anyMatch(r -> !r.isValid())); } + + @Test + void checkDuplicateFullNamesWithCaseInsensitivity() { + validator.validate("Journal of Physics", "J. Phys.", 1); + validator.validate("JOURNAL OF PHYSICS", "J. Phys.", 2); + + var issues = validator.getIssues(); + assertFalse(issues.isEmpty()); + assertTrue(issues.stream() + .anyMatch(issue -> issue.getMessage().contains("Duplicate full name"))); + } + + @Test + void checkDuplicateAbbreviationsWithCaseInsensitivity() { + validator.validate("Journal of Physics", "J. Phys.", 1); + validator.validate("Journal of Physiology", "j. phys.", 2); + + var issues = validator.getIssues(); + assertFalse(issues.isEmpty()); + assertTrue(issues.stream() + .anyMatch(issue -> issue.getMessage().contains("Duplicate abbreviation"))); + } } From ec45ee84efa644077dfb09fd27d37b9a66663821 Mon Sep 17 00:00:00 2001 From: Aspasia Tsagkaropoulou Date: Fri, 30 May 2025 13:55:28 +0300 Subject: [PATCH 22/35] Add AbbreviationEqualsFullTextWithSpecialCharacters test case --- .../journals/JournalAbbreviationValidatorTest.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java b/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java index 1ddbffc3c6b..5a827ad9b02 100644 --- a/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java +++ b/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java @@ -315,4 +315,16 @@ void checkDuplicateAbbreviationsWithCaseInsensitivity() { assertTrue(issues.stream() .anyMatch(issue -> issue.getMessage().contains("Duplicate abbreviation"))); } + + @Test + void checkAbbreviationEqualsFullTextWithSpecialCharacters() { + String fullName = "Physics & Chemistry"; + String abbreviation = "Physics & Chemistry"; + + var result = validator.checkAbbreviationEqualsFullText(fullName, abbreviation, 1); + assertFalse(result.isValid()); + assertEquals("Abbreviation is the same as the full text", result.getMessage()); + assertTrue(result.getSuggestion().contains("shorter abbreviation")); + } + } From 089b8044d2a01a0fea819faec2a66cdaba2aa9bd Mon Sep 17 00:00:00 2001 From: Aspasia Tsagkaropoulou Date: Fri, 30 May 2025 13:57:14 +0300 Subject: [PATCH 23/35] Add outdated variations test cases --- .../JournalAbbreviationValidatorTest.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java b/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java index 5a827ad9b02..1e6c4791fb1 100644 --- a/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java +++ b/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java @@ -327,4 +327,20 @@ void checkAbbreviationEqualsFullTextWithSpecialCharacters() { assertTrue(result.getSuggestion().contains("shorter abbreviation")); } + @Test + void checkOutdatedManagementAbbreviationWithVariations() { + String[] invalidAbbreviations = { + "Manage. Sci.", + "Manage Sci.", + "Manage.Sci.", + "Manage. Sci" + }; + + for (String abbreviation : invalidAbbreviations) { + var result = validator.checkOutdatedManagementAbbreviation("Management Science", abbreviation, 1); + assertFalse(result.isValid()); + assertTrue(result.getMessage().contains("outdated")); + assertTrue(result.getSuggestion().contains("Manag.")); + } + } } From 36691c536b5cc7ccf4d2875ef023e8e229dcc21d Mon Sep 17 00:00:00 2001 From: Aspasia Tsagkaropoulou Date: Fri, 30 May 2025 13:59:13 +0300 Subject: [PATCH 24/35] Add long input test case --- .../journals/JournalAbbreviationValidatorTest.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java b/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java index 1e6c4791fb1..3f1472f392a 100644 --- a/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java +++ b/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java @@ -343,4 +343,15 @@ void checkOutdatedManagementAbbreviationWithVariations() { assertTrue(result.getSuggestion().contains("Manag.")); } } + + @Test + void validateWithVeryLongInputs() { + String longName = "A".repeat(1000); + String longAbbr = "B".repeat(1000); + + var results = validator.validate(longName, longAbbr, 1); + assertFalse(results.isEmpty()); + // Should still perform all validations without throwing exceptions + assertEquals(5, results.size()); + } } From a2cf0c0070cd1f03c30c212c7e96db3e93e2ab76 Mon Sep 17 00:00:00 2001 From: Aspasia Tsagkaropoulou Date: Fri, 30 May 2025 14:00:34 +0300 Subject: [PATCH 25/35] Add all checks validation test case --- .../JournalAbbreviationValidatorTest.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java b/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java index 3f1472f392a..45a6312fa7e 100644 --- a/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java +++ b/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java @@ -354,4 +354,21 @@ void validateWithVeryLongInputs() { // Should still perform all validations without throwing exceptions assertEquals(5, results.size()); } + + @Test + void validateWithAllChecks() { + String fullName = "Zeszyty Naukowe Wy\\"; + String abbreviation = "Problemy Mat."; + + var results = validator.validate(fullName, abbreviation, 1); + assertFalse(results.isEmpty()); + + // Should have 5 results (3 error checks + 2 warning checks) + assertEquals(5, results.size()); + + // First result should be the wrong escape check + assertFalse(results.get(0).isValid()); + assertEquals("Invalid escape sequence in full name at position 15", results.get(0).getMessage()); + assertTrue(results.get(0).getSuggestion().contains("valid escape sequences")); + } } From 163cef4ecd011577d06d632ba6de7499eb001d2b Mon Sep 17 00:00:00 2001 From: Aspasia Tsagkaropoulou Date: Mon, 2 Jun 2025 15:50:41 +0300 Subject: [PATCH 26/35] Modify validation methods to return Optional instead of ValidationResult --- .../JournalAbbreviationValidator.java | 65 ++++++++----------- 1 file changed, 28 insertions(+), 37 deletions(-) diff --git a/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java b/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java index 1f791d8543a..dece87e9f01 100644 --- a/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java +++ b/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java @@ -5,6 +5,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -96,7 +97,7 @@ public enum ValidationType { /** * Checks if the journal name or abbreviation contains wrong escape characters */ - public ValidationResult checkWrongEscape(String fullName, String abbreviation, int lineNumber) { + public Optional checkWrongEscape(String fullName, String abbreviation, int lineNumber) { List escapeIssues = new ArrayList<>(); // Check full name @@ -124,24 +125,24 @@ public ValidationResult checkWrongEscape(String fullName, String abbreviation, i } return escapeIssues.isEmpty() ? - new ValidationResult(true, "", ValidationType.ERROR, fullName, abbreviation, lineNumber) : - escapeIssues.get(0); + Optional.of(new ValidationResult(true, "", ValidationType.ERROR, fullName, abbreviation, lineNumber)) : + Optional.of(escapeIssues.get(0)); } /** * Checks if the journal name or abbreviation contains non-UTF8 characters */ - public ValidationResult checkNonUtf8(String fullName, String abbreviation, int lineNumber) { + public Optional checkNonUtf8(String fullName, String abbreviation, int lineNumber) { if (!isValidUtf8(fullName) || !isValidUtf8(abbreviation)) { - return new ValidationResult(false, + return Optional.of(new ValidationResult(false, "Journal name or abbreviation contains invalid UTF-8 sequences", ValidationType.ERROR, fullName, abbreviation, lineNumber, - "Ensure all characters are valid UTF-8. Remove or replace any invalid characters."); + "Ensure all characters are valid UTF-8. Remove or replace any invalid characters.")); } - return new ValidationResult(true, "", ValidationType.ERROR, fullName, abbreviation, lineNumber); + return Optional.of(new ValidationResult(true, "", ValidationType.ERROR, fullName, abbreviation, lineNumber)); } private boolean isValidUtf8(String str) { @@ -156,19 +157,19 @@ private boolean isValidUtf8(String str) { /** * Checks if the abbreviation starts with the same letter as the full name */ - public ValidationResult checkStartingLetters(String fullName, String abbreviation, int lineNumber) { + public Optional checkStartingLetters(String fullName, String abbreviation, int lineNumber) { fullName = fullName.trim(); abbreviation = abbreviation.trim(); if (isAllowedException(fullName, abbreviation)) { - return new ValidationResult( + return Optional.of(new ValidationResult( true, "Allowed abbreviation exception", ValidationType.ERROR, fullName, abbreviation, lineNumber - ); + )); } String fullFirst = getFirstSignificantWord(fullName); @@ -178,7 +179,7 @@ public ValidationResult checkStartingLetters(String fullName, String abbreviatio if (!abbrFirst.isEmpty() && !fullFirst.isEmpty() && !abbrFirst.toLowerCase().startsWith(fullFirst.substring(0, 1).toLowerCase())) { - return new ValidationResult( + return Optional.of(new ValidationResult( false, "Abbreviation does not begin with same letter as full journal name", ValidationType.ERROR, @@ -188,17 +189,17 @@ public ValidationResult checkStartingLetters(String fullName, String abbreviatio String.format("Should start with '%c' (from '%s')", fullFirst.toLowerCase().charAt(0), fullFirst) - ); + )); } - return new ValidationResult( + return Optional.of(new ValidationResult( true, "", ValidationType.ERROR, fullName, abbreviation, lineNumber - ); + )); } private boolean isAllowedException(String fullName, String abbreviation) { @@ -219,34 +220,34 @@ private String getFirstSignificantWord(String s) { /** * Checks if the abbreviation is the same as the full text */ - public ValidationResult checkAbbreviationEqualsFullText(String fullName, String abbreviation, int lineNumber) { + public Optional checkAbbreviationEqualsFullText(String fullName, String abbreviation, int lineNumber) { if (fullName.equalsIgnoreCase(abbreviation) && fullName.trim().split("\\s+").length > 1) { - return new ValidationResult(false, + return Optional.of(new ValidationResult(false, "Abbreviation is the same as the full text", ValidationType.WARNING, fullName, abbreviation, lineNumber, - "Consider using a shorter abbreviation to distinguish it from the full name"); + "Consider using a shorter abbreviation to distinguish it from the full name")); } - return new ValidationResult(true, "", ValidationType.ERROR, fullName, abbreviation, lineNumber); + return Optional.of(new ValidationResult(true, "", ValidationType.ERROR, fullName, abbreviation, lineNumber)); } /** * Checks if the abbreviation uses outdated "Manage." instead of "Manag." */ - public ValidationResult checkOutdatedManagementAbbreviation(String fullName, String abbreviation, int lineNumber) { + public Optional checkOutdatedManagementAbbreviation(String fullName, String abbreviation, int lineNumber) { if (fullName.contains("Management") && abbreviation.contains("Manage.")) { - return new ValidationResult(false, + return Optional.of(new ValidationResult(false, "Management is abbreviated with outdated \"Manage.\" instead of \"Manag.\"", ValidationType.WARNING, fullName, abbreviation, lineNumber, - "Update to use the standard abbreviation \"Manag.\""); + "Update to use the standard abbreviation \"Manag.\"")); } - return new ValidationResult(true, "", ValidationType.WARNING, fullName, abbreviation, lineNumber); + return Optional.of(new ValidationResult(true, "", ValidationType.WARNING, fullName, abbreviation, lineNumber)); } /** @@ -298,13 +299,13 @@ public List validate(String fullName, String abbreviation, int List results = new ArrayList<>(); // Error checks - results.add(checkWrongEscape(fullName, abbreviation, lineNumber)); - results.add(checkNonUtf8(fullName, abbreviation, lineNumber)); - results.add(checkStartingLetters(fullName, abbreviation, lineNumber)); + checkWrongEscape(fullName, abbreviation, lineNumber).ifPresent(results::add); + checkNonUtf8(fullName, abbreviation, lineNumber).ifPresent(results::add); + checkStartingLetters(fullName, abbreviation, lineNumber).ifPresent(results::add); // Warning checks - results.add(checkAbbreviationEqualsFullText(fullName, abbreviation, lineNumber)); - results.add(checkOutdatedManagementAbbreviation(fullName, abbreviation, lineNumber)); + checkAbbreviationEqualsFullText(fullName, abbreviation, lineNumber).ifPresent(results::add); + checkOutdatedManagementAbbreviation(fullName, abbreviation, lineNumber).ifPresent(results::add); // Track for duplicate checks fullNameToAbbrev.computeIfAbsent(fullName, k -> new ArrayList<>()).add(abbreviation); @@ -312,14 +313,4 @@ public List validate(String fullName, String abbreviation, int return results; } - - /** - * Get all validation issues found - */ - public List getIssues() { - // Check for duplicates - checkDuplicateFullNames(); - checkDuplicateAbbreviations(); - return issues; - } } From 37187760afdc351b42e2d527120dd99061e61f1d Mon Sep 17 00:00:00 2001 From: Aspasia Tsagkaropoulou Date: Mon, 2 Jun 2025 16:05:15 +0300 Subject: [PATCH 27/35] Update test cases to handle the Optional returns --- .../JournalAbbreviationValidatorTest.java | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java b/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java index 45a6312fa7e..1ac966c87dc 100644 --- a/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java +++ b/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java @@ -21,7 +21,7 @@ void checkWrongEscapeWithInvalidFullName() { String fullName = "Zeszyty Naukowe Wy\\"; String abbreviation = "Problemy Mat."; - var result = validator.checkWrongEscape(fullName, abbreviation, 1); + var result = validator.checkWrongEscape(fullName, abbreviation, 1).orElseThrow(); assertFalse(result.isValid()); assertEquals("Invalid escape sequence in full name at position 15", result.getMessage()); assertEquals("Use valid escape sequences: \\\\, \\\", \\n, \\t, \\r, \\b, \\f, \\uXXXX", result.getSuggestion()); @@ -33,7 +33,7 @@ void checkWrongEscapeWithInvalidAbbreviation() { String fullName = "Journal of Evolutionary Biochemistry and Physiology"; String abbreviation = "J. Evol. Biochem. Physiol.\\"; - var result = validator.checkWrongEscape(fullName, abbreviation, 2); + var result = validator.checkWrongEscape(fullName, abbreviation, 2).orElseThrow(); assertFalse(result.isValid()); assertEquals("Invalid escape sequence in abbreviation at position 22", result.getMessage()); assertEquals("Use valid escape sequences: \\\\, \\\", \\n, \\t, \\r, \\b, \\f, \\uXXXX", result.getSuggestion()); @@ -45,7 +45,7 @@ void checkWrongEscapeWithValidEscapes() { String fullName = "Journal with \\n newline and \\t tab"; String abbreviation = "J. with \\r return and \\b backspace"; - var result = validator.checkWrongEscape(fullName, abbreviation, 1); + var result = validator.checkWrongEscape(fullName, abbreviation, 1).orElseThrow(); assertTrue(result.isValid()); assertEquals("", result.getMessage()); assertEquals("", result.getSuggestion()); @@ -56,7 +56,7 @@ void checkWrongEscapeWithMultipleIssues() { String fullName = "Journal with \\x invalid"; String abbreviation = "J. with \\y invalid"; - var result = validator.checkWrongEscape(fullName, abbreviation, 1); + var result = validator.checkWrongEscape(fullName, abbreviation, 1).orElseThrow(); assertFalse(result.isValid()); assertTrue(result.getMessage().startsWith("Invalid escape sequence")); assertTrue(result.getSuggestion().contains("valid escape sequences")); @@ -67,7 +67,7 @@ void checkNonUtf8WithValidInput() { String fullName = "Journal of Physics"; String abbreviation = "J. Phys."; - var result = validator.checkNonUtf8(fullName, abbreviation, 1); + var result = validator.checkNonUtf8(fullName, abbreviation, 1).orElseThrow(); assertTrue(result.isValid()); assertEquals("", result.getMessage()); assertEquals("", result.getSuggestion()); @@ -79,7 +79,7 @@ void checkNonUtf8WithInvalidInput() { String fullName = "Journal of Physics\uFFFD"; String abbreviation = "J. Phys."; - var result = validator.checkNonUtf8(fullName, abbreviation, 1); + var result = validator.checkNonUtf8(fullName, abbreviation, 1).orElseThrow(); assertFalse(result.isValid()); assertEquals("Journal name or abbreviation contains invalid UTF-8 sequences", result.getMessage()); assertEquals("Ensure all characters are valid UTF-8. Remove or replace any invalid characters.", result.getSuggestion()); @@ -90,7 +90,7 @@ void checkStartingLettersWithValidInput() { String fullName = "Journal of Physics"; String abbreviation = "J. Phys."; - var result = validator.checkStartingLetters(fullName, abbreviation, 1); + var result = validator.checkStartingLetters(fullName, abbreviation, 1).orElseThrow(); assertTrue(result.isValid()); assertEquals("", result.getMessage()); assertEquals("", result.getSuggestion()); @@ -101,7 +101,7 @@ void checkStartingLettersWithInvalidInput() { String fullName = "Journal of Physics"; String abbreviation = "Phys. J."; - var result = validator.checkStartingLetters(fullName, abbreviation, 1); + var result = validator.checkStartingLetters(fullName, abbreviation, 1).orElseThrow(); assertFalse(result.isValid()); assertEquals("Abbreviation does not begin with same letter as full journal name", result.getMessage()); assertEquals("Should start with 'j' (from 'Journal')", result.getSuggestion()); @@ -112,7 +112,7 @@ void checkStartingLettersWithThePrefix() { String fullName = "The Journal of Physics"; String abbreviation = "J. Phys."; - var result = validator.checkStartingLetters(fullName, abbreviation, 1); + var result = validator.checkStartingLetters(fullName, abbreviation, 1).orElseThrow(); assertTrue(result.isValid()); assertEquals("", result.getMessage()); assertEquals("", result.getSuggestion()); @@ -123,7 +123,7 @@ void checkStartingLettersWithAPrefix() { String fullName = "A Journal of Physics"; String abbreviation = "J. Phys."; - var result = validator.checkStartingLetters(fullName, abbreviation, 1); + var result = validator.checkStartingLetters(fullName, abbreviation, 1).orElseThrow(); assertTrue(result.isValid()); assertEquals("", result.getMessage()); assertEquals("", result.getSuggestion()); @@ -134,7 +134,7 @@ void checkAbbreviationEqualsFullTextWithValidInput() { String fullName = "Journal of Physics"; String abbreviation = "J. Phys."; - var result = validator.checkAbbreviationEqualsFullText(fullName, abbreviation, 1); + var result = validator.checkAbbreviationEqualsFullText(fullName, abbreviation, 1).orElseThrow(); assertTrue(result.isValid()); assertEquals("", result.getMessage()); assertEquals("", result.getSuggestion()); @@ -145,7 +145,7 @@ void checkAbbreviationEqualsFullTextWithInvalidInput() { String fullName = "Quantum"; String abbreviation = "Quantum"; - var result = validator.checkAbbreviationEqualsFullText(fullName, abbreviation, 1); + var result = validator.checkAbbreviationEqualsFullText(fullName, abbreviation, 1).orElseThrow(); assertFalse(result.isValid()); assertEquals("Abbreviation is the same as the full text", result.getMessage()); assertEquals("Consider using a shorter abbreviation to distinguish it from the full name", result.getSuggestion()); @@ -156,7 +156,7 @@ void checkOutdatedManagementAbbreviationWithValidInput() { String fullName = "Management Science"; String abbreviation = "Manag. Sci."; - var result = validator.checkOutdatedManagementAbbreviation(fullName, abbreviation, 1); + var result = validator.checkOutdatedManagementAbbreviation(fullName, abbreviation, 1).orElseThrow(); assertTrue(result.isValid()); assertEquals("", result.getMessage()); assertEquals("", result.getSuggestion()); @@ -167,7 +167,7 @@ void checkOutdatedManagementAbbreviationWithInvalidInput() { String fullName = "Management Science"; String abbreviation = "Manage. Sci."; - var result = validator.checkOutdatedManagementAbbreviation(fullName, abbreviation, 1); + var result = validator.checkOutdatedManagementAbbreviation(fullName, abbreviation, 1).orElseThrow(); assertFalse(result.isValid()); assertEquals("Management is abbreviated with outdated \"Manage.\" instead of \"Manag.\"", result.getMessage()); assertEquals("Update to use the standard abbreviation \"Manag.\"", result.getSuggestion()); @@ -208,7 +208,7 @@ void checkWrongEscapeWithMultipleValidEscapes() { String fullName = "Journal with \\n\\t\\r\\b\\f\\\"\\\\"; String abbreviation = "J. with \\n\\t\\r\\b\\f\\\"\\\\"; - var result = validator.checkWrongEscape(fullName, abbreviation, 1); + var result = validator.checkWrongEscape(fullName, abbreviation, 1).orElseThrow(); assertTrue(result.isValid()); assertEquals("", result.getMessage()); assertEquals("", result.getSuggestion()); @@ -219,7 +219,7 @@ void checkWrongEscapeWithUnicodeEscape() { String fullName = "Journal with \\u0041"; String abbreviation = "J. with \\u0042"; - var result = validator.checkWrongEscape(fullName, abbreviation, 1); + var result = validator.checkWrongEscape(fullName, abbreviation, 1).orElseThrow(); assertTrue(result.isValid()); assertEquals("", result.getMessage()); assertEquals("", result.getSuggestion()); @@ -230,7 +230,7 @@ void checkWrongEscapeWithInvalidUnicodeEscape() { String fullName = "Journal with \\u004"; String abbreviation = "J. Phys."; - var result = validator.checkWrongEscape(fullName, abbreviation, 1); + var result = validator.checkWrongEscape(fullName, abbreviation, 1).orElseThrow(); assertFalse(result.isValid()); assertEquals("Invalid escape sequence in full name at position 13", result.getMessage()); assertTrue(result.getSuggestion().contains("valid escape sequences")); @@ -241,7 +241,7 @@ void checkNonUtf8WithMultipleInvalidCharacters() { String fullName = "Journal of Physics\uFFFD\uFFFD"; String abbreviation = "J. Phys.\uFFFD"; - var result = validator.checkNonUtf8(fullName, abbreviation, 1); + var result = validator.checkNonUtf8(fullName, abbreviation, 1).orElseThrow(); assertFalse(result.isValid()); assertEquals("Journal name or abbreviation contains invalid UTF-8 sequences", result.getMessage()); assertTrue(result.getSuggestion().contains("valid UTF-8")); @@ -252,7 +252,7 @@ void checkStartingLettersWithMultiplePrefixes() { String fullName = "The A An Journal of Physics"; String abbreviation = "J. Phys."; - var result = validator.checkStartingLetters(fullName, abbreviation, 1); + var result = validator.checkStartingLetters(fullName, abbreviation, 1).orElseThrow(); assertTrue(result.isValid()); assertEquals("", result.getMessage()); assertEquals("", result.getSuggestion()); @@ -263,7 +263,7 @@ void checkStartingLettersWithSpecialCharacters() { String fullName = "The Journal of Physics & Chemistry"; String abbreviation = "J. Phys. Chem."; - var result = validator.checkStartingLetters(fullName, abbreviation, 1); + var result = validator.checkStartingLetters(fullName, abbreviation, 1).orElseThrow(); assertTrue(result.isValid()); assertEquals("", result.getMessage()); assertEquals("", result.getSuggestion()); @@ -274,7 +274,7 @@ void checkStartingLettersWithNumbers() { String fullName = "2D Materials"; String abbreviation = "2D Mater."; - var result = validator.checkStartingLetters(fullName, abbreviation, 1); + var result = validator.checkStartingLetters(fullName, abbreviation, 1).orElseThrow(); assertTrue(result.isValid()); assertEquals("", result.getMessage()); assertEquals("", result.getSuggestion()); @@ -321,7 +321,7 @@ void checkAbbreviationEqualsFullTextWithSpecialCharacters() { String fullName = "Physics & Chemistry"; String abbreviation = "Physics & Chemistry"; - var result = validator.checkAbbreviationEqualsFullText(fullName, abbreviation, 1); + var result = validator.checkAbbreviationEqualsFullText(fullName, abbreviation, 1).orElseThrow(); assertFalse(result.isValid()); assertEquals("Abbreviation is the same as the full text", result.getMessage()); assertTrue(result.getSuggestion().contains("shorter abbreviation")); @@ -337,7 +337,7 @@ void checkOutdatedManagementAbbreviationWithVariations() { }; for (String abbreviation : invalidAbbreviations) { - var result = validator.checkOutdatedManagementAbbreviation("Management Science", abbreviation, 1); + var result = validator.checkOutdatedManagementAbbreviation("Management Science", abbreviation, 1).orElseThrow(); assertFalse(result.isValid()); assertTrue(result.getMessage().contains("outdated")); assertTrue(result.getSuggestion().contains("Manag.")); From 488ac8caebb2dd3ff74fa5311f68cc9bfbc47a3d Mon Sep 17 00:00:00 2001 From: Aspasia Tsagkaropoulou Date: Mon, 2 Jun 2025 16:09:06 +0300 Subject: [PATCH 28/35] Add getissues() and Optional.empty() to validation methods --- .../JournalAbbreviationValidator.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java b/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java index dece87e9f01..93de3044291 100644 --- a/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java +++ b/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java @@ -94,10 +94,21 @@ public enum ValidationType { private final Map> fullNameToAbbrev = new HashMap<>(); private final Map> abbrevToFullName = new HashMap<>(); + /** + * Returns the list of validation issues found during validation + */ + public List getIssues() { + return new ArrayList<>(issues); + } + /** * Checks if the journal name or abbreviation contains wrong escape characters */ public Optional checkWrongEscape(String fullName, String abbreviation, int lineNumber) { + if (fullName == null || abbreviation == null) { + return Optional.empty(); + } + List escapeIssues = new ArrayList<>(); // Check full name @@ -133,6 +144,10 @@ public Optional checkWrongEscape(String fullName, String abbre * Checks if the journal name or abbreviation contains non-UTF8 characters */ public Optional checkNonUtf8(String fullName, String abbreviation, int lineNumber) { + if (fullName == null || abbreviation == null) { + return Optional.empty(); + } + if (!isValidUtf8(fullName) || !isValidUtf8(abbreviation)) { return Optional.of(new ValidationResult(false, "Journal name or abbreviation contains invalid UTF-8 sequences", @@ -158,6 +173,10 @@ private boolean isValidUtf8(String str) { * Checks if the abbreviation starts with the same letter as the full name */ public Optional checkStartingLetters(String fullName, String abbreviation, int lineNumber) { + if (fullName == null || abbreviation == null) { + return Optional.empty(); + } + fullName = fullName.trim(); abbreviation = abbreviation.trim(); @@ -221,6 +240,10 @@ private String getFirstSignificantWord(String s) { * Checks if the abbreviation is the same as the full text */ public Optional checkAbbreviationEqualsFullText(String fullName, String abbreviation, int lineNumber) { + if (fullName == null || abbreviation == null) { + return Optional.empty(); + } + if (fullName.equalsIgnoreCase(abbreviation) && fullName.trim().split("\\s+").length > 1) { return Optional.of(new ValidationResult(false, "Abbreviation is the same as the full text", @@ -238,6 +261,10 @@ public Optional checkAbbreviationEqualsFullText(String fullNam * Checks if the abbreviation uses outdated "Manage." instead of "Manag." */ public Optional checkOutdatedManagementAbbreviation(String fullName, String abbreviation, int lineNumber) { + if (fullName == null || abbreviation == null) { + return Optional.empty(); + } + if (fullName.contains("Management") && abbreviation.contains("Manage.")) { return Optional.of(new ValidationResult(false, "Management is abbreviated with outdated \"Manage.\" instead of \"Manag.\"", From 76392ecf42ebaf56a78622d0712a33cc29de1d66 Mon Sep 17 00:00:00 2001 From: Aspasia Tsagkaropoulou Date: Wed, 4 Jun 2025 01:26:48 +0300 Subject: [PATCH 29/35] Add runtime validation and connect validator with JabKit --- .../org/jabref/cli/ArgumentProcessor.java | 886 ++++++++++++++++++ src/main/java/org/jabref/cli/CliOptions.java | 419 +++++++++ 2 files changed, 1305 insertions(+) create mode 100644 src/main/java/org/jabref/cli/ArgumentProcessor.java create mode 100644 src/main/java/org/jabref/cli/CliOptions.java diff --git a/src/main/java/org/jabref/cli/ArgumentProcessor.java b/src/main/java/org/jabref/cli/ArgumentProcessor.java new file mode 100644 index 00000000000..da8db97e300 --- /dev/null +++ b/src/main/java/org/jabref/cli/ArgumentProcessor.java @@ -0,0 +1,886 @@ +package org.jabref.cli; + +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.net.MalformedURLException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Optional; +import java.util.Set; +import java.util.prefs.BackingStoreException; + +import org.jabref.logic.FilePreferences; +import org.jabref.logic.JabRefException; +import org.jabref.logic.UiCommand; +import org.jabref.logic.bibtex.FieldPreferences; +import org.jabref.logic.citationkeypattern.CitationKeyGenerator; +import org.jabref.logic.exporter.AtomicFileWriter; +import org.jabref.logic.exporter.BibDatabaseWriter; +import org.jabref.logic.exporter.BibWriter; +import org.jabref.logic.exporter.BibtexDatabaseWriter; +import org.jabref.logic.exporter.EmbeddedBibFilePdfExporter; +import org.jabref.logic.exporter.Exporter; +import org.jabref.logic.exporter.ExporterFactory; +import org.jabref.logic.exporter.SelfContainedSaveConfiguration; +import org.jabref.logic.exporter.XmpPdfExporter; +import org.jabref.logic.importer.FetcherException; +import org.jabref.logic.importer.ImportException; +import org.jabref.logic.importer.ImportFormatPreferences; +import org.jabref.logic.importer.ImportFormatReader; +import org.jabref.logic.importer.OpenDatabase; +import org.jabref.logic.importer.ParseException; +import org.jabref.logic.importer.ParserResult; +import org.jabref.logic.importer.SearchBasedFetcher; +import org.jabref.logic.importer.WebFetchers; +import org.jabref.logic.importer.fileformat.BibtexParser; +import org.jabref.logic.journals.JournalAbbreviationRepository; +import org.jabref.logic.l10n.Localization; +import org.jabref.logic.net.URLDownload; +import org.jabref.logic.os.OS; +import org.jabref.logic.preferences.CliPreferences; +import org.jabref.logic.quality.consistency.BibliographyConsistencyCheck; +import org.jabref.logic.quality.consistency.BibliographyConsistencyCheckResultCsvWriter; +import org.jabref.logic.quality.consistency.BibliographyConsistencyCheckResultTxtWriter; +import org.jabref.logic.quality.consistency.BibliographyConsistencyCheckResultWriter; +import org.jabref.logic.search.DatabaseSearcher; +import org.jabref.logic.search.SearchPreferences; +import org.jabref.logic.shared.prefs.SharedDatabasePreferences; +import org.jabref.logic.util.CurrentThreadTaskExecutor; +import org.jabref.logic.util.io.FileUtil; +import org.jabref.logic.xmp.XmpPreferences; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.database.BibDatabaseMode; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.search.query.SearchQuery; +import org.jabref.model.strings.StringUtil; +import org.jabref.model.util.DummyFileUpdateMonitor; +import org.jabref.model.util.FileUpdateMonitor; + +import com.airhacks.afterburner.injection.Injector; +import com.google.common.base.Throwables; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ArgumentProcessor { + private static final Logger LOGGER = LoggerFactory.getLogger(ArgumentProcessor.class); + + public enum Mode { INITIAL_START, REMOTE_START } + + private final CliOptions cli; + + private final Mode startupMode; + + private final CliPreferences cliPreferences; + private final FileUpdateMonitor fileUpdateMonitor; + private final BibEntryTypesManager entryTypesManager; + + private boolean guiNeeded; + private final List uiCommands = new ArrayList<>(); + + /** + * First call the constructor, then call {@link #processArguments()}. + * Afterward, you can access the {@link #getUiCommands()}. + * + * @implNote both cli and gui preferences are passed to make the dependency to GUI parts explicit + */ + public ArgumentProcessor(String[] args, + Mode startupMode, + CliPreferences cliPreferences, + FileUpdateMonitor fileUpdateMonitor, + BibEntryTypesManager entryTypesManager) + throws org.apache.commons.cli.ParseException { + this.cli = new CliOptions(args); + this.startupMode = startupMode; + this.cliPreferences = cliPreferences; + this.fileUpdateMonitor = fileUpdateMonitor; + this.entryTypesManager = entryTypesManager; + } + + /** + * Will open a file (like {@link #importFile(String)}, but will also request JabRef to focus on this library. + * + * @return ParserResult with setToOpenTab(true) + */ + private Optional importToOpenBase(String importArguments) { + Optional result = importFile(importArguments); + result.ifPresent(ParserResult::setToOpenTab); + return result; + } + + private Optional importBibtexToOpenBase(String argument, ImportFormatPreferences importFormatPreferences) { + BibtexParser parser = new BibtexParser(importFormatPreferences); + try { + List entries = parser.parseEntries(argument); + ParserResult result = new ParserResult(entries); + result.setToOpenTab(); + return Optional.of(result); + } catch (ParseException e) { + System.err.println(Localization.lang("Error occurred when parsing entry") + ": " + e.getLocalizedMessage()); + return Optional.empty(); + } + } + + /** + * + * @param importArguments Format: fileName[,format] + */ + private Optional importFile(String importArguments) { + LOGGER.debug("Importing file {}", importArguments); + String[] data = importArguments.split(","); + + String address = data[0]; + Path file; + if (address.startsWith("http://") || address.startsWith("https://") || address.startsWith("ftp://")) { + // Download web resource to temporary file + try { + file = new URLDownload(address).toTemporaryFile(); + } catch (FetcherException | MalformedURLException e) { + System.err.println(Localization.lang("Problem downloading from %1", address) + e.getLocalizedMessage()); + return Optional.empty(); + } + } else { + if (OS.WINDOWS) { + file = Path.of(address); + } else { + file = Path.of(address.replace("~", System.getProperty("user.home"))); + } + } + + String importFormat; + if (data.length > 1) { + importFormat = data[1]; + } else { + importFormat = "*"; + } + + Optional importResult = importFile(file, importFormat); + importResult.ifPresent(result -> { + if (result.hasWarnings()) { + System.out.println(result.getErrorMessage()); + } + }); + return importResult; + } + + private Optional importFile(Path file, String importFormat) { + try { + ImportFormatReader importFormatReader = new ImportFormatReader( + cliPreferences.getImporterPreferences(), + cliPreferences.getImportFormatPreferences(), + cliPreferences.getCitationKeyPatternPreferences(), + fileUpdateMonitor + ); + + if (!"*".equals(importFormat)) { + System.out.println(Localization.lang("Importing %0", file)); + ParserResult result = importFormatReader.importFromFile(importFormat, file); + return Optional.of(result); + } else { + // * means "guess the format": + System.out.println(Localization.lang("Importing file %0 as unknown format", file)); + + ImportFormatReader.UnknownFormatImport importResult = + importFormatReader.importUnknownFormat(file, new DummyFileUpdateMonitor()); + + System.out.println(Localization.lang("Format used: %0", importResult.format())); + return Optional.of(importResult.parserResult()); + } + } catch (ImportException ex) { + System.err.println(Localization.lang("Error opening file '%0'", file) + "\n" + ex.getLocalizedMessage()); + return Optional.empty(); + } + } + + public void processArguments() { + uiCommands.clear(); + + if ((startupMode == Mode.INITIAL_START) && cli.isShowVersion()) { + cli.displayVersion(); + } + + if ((startupMode == Mode.INITIAL_START) && cli.isHelp()) { + CliOptions.printUsage(cliPreferences); + guiNeeded = false; + return; + } + + guiNeeded = true; + + // Check if we should reset all preferences to default values: + if (cli.isPreferencesReset()) { + resetPreferences(cli.getPreferencesReset()); + } + + // Check if we should import preferences from a file: + if (cli.isPreferencesImport()) { + importPreferences(); + } + + List loaded = importAndOpenFiles(); + + if (!cli.isBlank() && cli.isFetcherEngine()) { + fetch(cli.getFetcherEngine()).ifPresent(loaded::add); + } + + if (cli.isExportMatches()) { + if (!loaded.isEmpty()) { + if (!exportMatches(loaded)) { + return; + } + } else { + System.err.println(Localization.lang("The output option depends on a valid input option.")); + } + } + + if (cli.isGenerateCitationKeys()) { + regenerateCitationKeys(loaded); + } + + if ((cli.isWriteXmpToPdf() && cli.isEmbedBibFileInPdf()) || (cli.isWriteMetadataToPdf() && (cli.isWriteXmpToPdf() || cli.isEmbedBibFileInPdf()))) { + System.err.println("Give only one of [writeXmpToPdf, embedBibFileInPdf, writeMetadataToPdf]"); + } + + if (cli.isWriteMetadataToPdf() || cli.isWriteXmpToPdf() || cli.isEmbedBibFileInPdf()) { + if (!loaded.isEmpty()) { + writeMetadataToPdf(loaded, + cli.getWriteMetadataToPdf(), + cliPreferences.getXmpPreferences(), + cliPreferences.getFilePreferences(), + cliPreferences.getLibraryPreferences().getDefaultBibDatabaseMode(), + cliPreferences.getCustomEntryTypesRepository(), + cliPreferences.getFieldPreferences(), + Injector.instantiateModelOrService(JournalAbbreviationRepository.class), + cli.isWriteXmpToPdf() || cli.isWriteMetadataToPdf(), + cli.isEmbedBibFileInPdf() || cli.isWriteMetadataToPdf()); + } + } + + if (cli.isFileExport()) { + if (!loaded.isEmpty()) { + exportFile(loaded, cli.getFileExport().split(",")); + LOGGER.debug("Finished export"); + } else { + System.err.println(Localization.lang("The output option depends on a valid import option.")); + } + } + + if (cli.isPreferencesExport()) { + try { + cliPreferences.exportPreferences(Path.of(cli.getPreferencesExport())); + } catch (JabRefException ex) { + LOGGER.error("Cannot export preferences", ex); + } + } + + if (!cli.isBlank() && cli.isAuxImport()) { + doAuxImport(loaded); + } + + if (cli.isBlank()) { + uiCommands.add(new UiCommand.BlankWorkspace()); + } + + if (!cli.isBlank() && cli.isJumpToKey()) { + uiCommands.add(new UiCommand.JumpToEntryKey(cli.getJumpToKey())); + } + + if (!cli.isBlank() && !loaded.isEmpty()) { + uiCommands.add(new UiCommand.OpenDatabases(loaded)); + } + + if (cli.isBlank() && loaded.isEmpty()) { + uiCommands.add(new UiCommand.BlankWorkspace()); + } + + if (cli.isCheckConsistency()) { + if (!loaded.isEmpty()) { + checkConsistency(loaded, cli.getCheckConsistency(), cli.getCheckConsistencyOutputFormat()); + } else { + System.err.println(Localization.lang("The consistency check option depends on a valid import option.")); + } + } + + if (cli.isValidateJournals()) { + validateJournalAbbreviations(); + } + } + + private void checkConsistency(List loaded, + String fileName, + String outputFormat) { + Optional fileNameOpt = Optional.ofNullable(fileName); + Optional outputFormatOpt = Optional.ofNullable(outputFormat); + + if (fileNameOpt.isEmpty()) { + System.out.println(Localization.lang("No file specified for consistency check.")); + return; + } + + Path filePath = Path.of(fileNameOpt.get()); + ParserResult pr; + try { + pr = OpenDatabase.loadDatabase(filePath, cliPreferences.getImportFormatPreferences(), fileUpdateMonitor); + } catch (IOException ex) { + LOGGER.error("Error reading '{}'.", filePath, ex); + return; + } + BibDatabaseContext databaseContext = pr.getDatabaseContext(); + List entries = databaseContext.getDatabase().getEntries(); + + BibliographyConsistencyCheck consistencyCheck = new BibliographyConsistencyCheck(); + BibliographyConsistencyCheck.Result result = consistencyCheck.check(entries); + + Writer writer = new OutputStreamWriter(System.out); + BibliographyConsistencyCheckResultWriter checkResultWriter; + if (outputFormatOpt.isEmpty() || "txt".equalsIgnoreCase(outputFormatOpt.get())) { + checkResultWriter = new BibliographyConsistencyCheckResultTxtWriter( + result, + writer, + cli.isPorcelainOutputMode(), + entryTypesManager, + databaseContext.getMode()); + } else { + checkResultWriter = new BibliographyConsistencyCheckResultCsvWriter( + result, + writer, + cli.isPorcelainOutputMode(), + entryTypesManager, + databaseContext.getMode()); + } + + // System.out should not be closed, therefore no try-with-resources + try { + checkResultWriter.writeFindings(); + writer.flush(); + } catch (IOException e) { + LOGGER.error("Error writing results", e); + } + if (!cli.isPorcelainOutputMode()) { + System.out.println(Localization.lang("Consistency check completed")); + } + } + + private static void writeMetadataToPdf(List loaded, + String filesAndCiteKeys, + XmpPreferences xmpPreferences, + FilePreferences filePreferences, + BibDatabaseMode databaseMode, + BibEntryTypesManager entryTypesManager, + FieldPreferences fieldPreferences, + JournalAbbreviationRepository abbreviationRepository, + boolean writeXMP, + boolean embeddBibfile) { + if (loaded.isEmpty()) { + LOGGER.error("The write xmp option depends on a valid import option."); + return; + } + ParserResult pr = loaded.getLast(); + BibDatabaseContext databaseContext = pr.getDatabaseContext(); + + XmpPdfExporter xmpPdfExporter = new XmpPdfExporter(xmpPreferences); + EmbeddedBibFilePdfExporter embeddedBibFilePdfExporter = new EmbeddedBibFilePdfExporter(databaseMode, entryTypesManager, fieldPreferences); + + if ("all".equals(filesAndCiteKeys)) { + for (BibEntry entry : databaseContext.getEntries()) { + writeMetadataToPDFsOfEntry( + databaseContext, + entry.getCitationKey().orElse(""), + entry, + filePreferences, + xmpPdfExporter, + embeddedBibFilePdfExporter, + abbreviationRepository, + writeXMP, + embeddBibfile); + } + return; + } + + List citeKeys = new ArrayList<>(); + List pdfs = new ArrayList<>(); + for (String fileOrCiteKey : filesAndCiteKeys.split(",")) { + if (fileOrCiteKey.toLowerCase(Locale.ROOT).endsWith(".pdf")) { + pdfs.add(fileOrCiteKey); + } else { + citeKeys.add(fileOrCiteKey); + } + } + + writeMetadataToPdfByCitekey( + databaseContext, + citeKeys, + filePreferences, + xmpPdfExporter, + embeddedBibFilePdfExporter, + abbreviationRepository, + writeXMP, + embeddBibfile); + writeMetadataToPdfByFileNames( + databaseContext, + pdfs, + filePreferences, + xmpPdfExporter, + embeddedBibFilePdfExporter, + abbreviationRepository, + writeXMP, + embeddBibfile); + } + + private static void writeMetadataToPDFsOfEntry(BibDatabaseContext databaseContext, + String citeKey, + BibEntry entry, + FilePreferences filePreferences, + XmpPdfExporter xmpPdfExporter, + EmbeddedBibFilePdfExporter embeddedBibFilePdfExporter, + JournalAbbreviationRepository abbreviationRepository, + boolean writeXMP, + boolean embedBibfile) { + try { + if (writeXMP) { + if (xmpPdfExporter.exportToAllFilesOfEntry(databaseContext, filePreferences, entry, List.of(entry), abbreviationRepository)) { + System.out.printf("Successfully written XMP metadata on at least one linked file of %s%n", citeKey); + } else { + System.err.printf("Cannot write XMP metadata on any linked files of %s. Make sure there is at least one linked file and the path is correct.%n", citeKey); + } + } + if (embedBibfile) { + if (embeddedBibFilePdfExporter.exportToAllFilesOfEntry(databaseContext, filePreferences, entry, List.of(entry), abbreviationRepository)) { + System.out.printf("Successfully embedded metadata on at least one linked file of %s%n", citeKey); + } else { + System.out.printf("Cannot embed metadata on any linked files of %s. Make sure there is at least one linked file and the path is correct.%n", citeKey); + } + } + } catch (Exception e) { + LOGGER.error("Failed writing metadata on a linked file of {}.", citeKey); + } + } + + private static void writeMetadataToPdfByCitekey(BibDatabaseContext databaseContext, + List citeKeys, + FilePreferences filePreferences, + XmpPdfExporter xmpPdfExporter, + EmbeddedBibFilePdfExporter embeddedBibFilePdfExporter, + JournalAbbreviationRepository abbreviationRepository, + boolean writeXMP, + boolean embeddBibfile) { + for (String citeKey : citeKeys) { + List bibEntryList = databaseContext.getDatabase().getEntriesByCitationKey(citeKey); + if (bibEntryList.isEmpty()) { + System.err.printf("Skipped - Cannot find %s in library.%n", citeKey); + continue; + } + for (BibEntry entry : bibEntryList) { + writeMetadataToPDFsOfEntry(databaseContext, citeKey, entry, filePreferences, xmpPdfExporter, embeddedBibFilePdfExporter, abbreviationRepository, writeXMP, embeddBibfile); + } + } + } + + private static void writeMetadataToPdfByFileNames(BibDatabaseContext databaseContext, + List pdfs, + FilePreferences filePreferences, + XmpPdfExporter xmpPdfExporter, + EmbeddedBibFilePdfExporter embeddedBibFilePdfExporter, + JournalAbbreviationRepository abbreviationRepository, + boolean writeXMP, + boolean embeddBibfile) { + for (String fileName : pdfs) { + Path filePath = Path.of(fileName); + if (!filePath.isAbsolute()) { + filePath = FileUtil.find(fileName, databaseContext.getFileDirectories(filePreferences)).orElse(FileUtil.find(fileName, List.of(Path.of("").toAbsolutePath())).orElse(filePath)); + } + if (Files.exists(filePath)) { + try { + if (writeXMP) { + if (xmpPdfExporter.exportToFileByPath(databaseContext, filePreferences, filePath, abbreviationRepository)) { + System.out.printf("Successfully written XMP metadata of at least one entry to %s%n", fileName); + } else { + System.out.printf("File %s is not linked to any entry in database.%n", fileName); + } + } + if (embeddBibfile) { + if (embeddedBibFilePdfExporter.exportToFileByPath(databaseContext, filePreferences, filePath, abbreviationRepository)) { + System.out.printf("Successfully embedded XMP metadata of at least one entry to %s%n", fileName); + } else { + System.out.printf("File %s is not linked to any entry in database.%n", fileName); + } + } + } catch (IOException e) { + LOGGER.error("Error accessing file '{}'.", fileName); + } catch (Exception e) { + LOGGER.error("Error writing entry to {}.", fileName); + } + } else { + LOGGER.error("Skipped - PDF {} does not exist", fileName); + } + } + } + + private boolean exportMatches(List loaded) { + String[] data = cli.getExportMatches().split(","); + String searchTerm = data[0].replace("\\$", " "); // enables blanks within the search term: + // $ stands for a blank + ParserResult pr = loaded.getLast(); + BibDatabaseContext databaseContext = pr.getDatabaseContext(); + + SearchPreferences searchPreferences = cliPreferences.getSearchPreferences(); + SearchQuery query = new SearchQuery(searchTerm, searchPreferences.getSearchFlags()); + + List matches; + try { + // extract current thread task executor from indexManager + matches = new DatabaseSearcher(query, databaseContext, new CurrentThreadTaskExecutor(), cliPreferences).getMatches(); + } catch (IOException e) { + LOGGER.error("Error occurred when searching", e); + return false; + } + + // export matches + if (!matches.isEmpty()) { + String formatName; + + // read in the export format, take default format if no format entered + switch (data.length) { + case 3 -> formatName = data[2]; + case 2 -> + // default exporter: bib file + formatName = "bib"; + default -> { + System.err.println(Localization.lang("Output file missing").concat(". \n \t ") + .concat(Localization.lang("Usage")).concat(": ") + CliOptions.getExportMatchesSyntax()); + guiNeeded = false; + return false; + } + } + + if ("bib".equals(formatName)) { + // output a bib file as default or if + // provided exportFormat is "bib" + saveDatabase(new BibDatabase(matches), data[1]); + LOGGER.debug("Finished export"); + } else { + // export new database + ExporterFactory exporterFactory = ExporterFactory.create(cliPreferences); + Optional exporter = exporterFactory.getExporterByName(formatName); + if (exporter.isEmpty()) { + System.err.println(Localization.lang("Unknown export format %0", formatName)); + } else { + // We have an TemplateExporter instance: + try { + System.out.println(Localization.lang("Exporting %0", data[1])); + exporter.get().export( + databaseContext, + Path.of(data[1]), + matches, + Collections.emptyList(), + Injector.instantiateModelOrService(JournalAbbreviationRepository.class)); + } catch (Exception ex) { + System.err.println(Localization.lang("Could not export file '%0' (reason: %1)", data[1], Throwables.getStackTraceAsString(ex))); + } + } + } + } else { + System.err.println(Localization.lang("No search matches.")); + } + return true; + } + + private void doAuxImport(List loaded) { + boolean usageMsg; + + if (!loaded.isEmpty()) { + usageMsg = generateAux(loaded, cli.getAuxImport().split(",")); + } else { + usageMsg = true; + } + + if (usageMsg) { + System.out.println(Localization.lang("no base-BibTeX-file specified!")); + System.out.println(Localization.lang("usage") + " :"); + System.out.println("jabref --aux infile[.aux],outfile[.bib] base-BibTeX-file"); + } + } + + /** + * @return List of opened files (could be .bib, but also other formats). May also contain error results. + */ + private List importAndOpenFiles() { + List loaded = new ArrayList<>(); + List toImport = new ArrayList<>(); + if (!cli.isBlank() && (!cli.getLeftOver().isEmpty())) { + for (String aLeftOver : cli.getLeftOver()) { + // Leftover arguments that have a "bib" extension are interpreted as + // BIB files to open. Other files, and files that could not be opened + // as bib, we try to import instead. + boolean bibExtension = aLeftOver.toLowerCase(Locale.ENGLISH).endsWith("bib"); + + ParserResult pr = new ParserResult(); + if (bibExtension) { + try { + pr = OpenDatabase.loadDatabase( + Path.of(aLeftOver), + cliPreferences.getImportFormatPreferences(), + fileUpdateMonitor); + // In contrast to org.jabref.gui.LibraryTab.onDatabaseLoadingSucceed, we do not execute OpenDatabaseAction.performPostOpenActions(result, dialogService); + } catch (IOException ex) { + pr = ParserResult.fromError(ex); + LOGGER.error("Error opening file '{}'", aLeftOver, ex); + } + } + + if (!bibExtension || (pr.isEmpty())) { + // We will try to import this file. Normally we + // will import it into a new tab, but if this import has + // been initiated by another instance through the remote + // listener, we will instead import it into the current library. + // This will enable easy integration with web browsers that can + // open a reference file in JabRef. + if (startupMode == Mode.INITIAL_START) { + toImport.add(aLeftOver); + } else { + loaded.add(importToOpenBase(aLeftOver).orElse(new ParserResult())); + } + } else { + loaded.add(pr); + } + } + } + + if (!cli.isBlank() && cli.isFileImport()) { + toImport.add(cli.getFileImport()); + } + + for (String filenameString : toImport) { + importFile(filenameString).ifPresent(loaded::add); + } + + if (!cli.isBlank() && cli.isImportToOpenBase()) { + importToOpenBase(cli.getImportToOpenBase()).ifPresent(loaded::add); + } + + if (!cli.isBlank() && cli.isBibtexImport()) { + importBibtexToOpenBase(cli.getBibtexImport(), cliPreferences.getImportFormatPreferences()).ifPresent(loaded::add); + } + + return loaded; + } + + private boolean generateAux(List loaded, String[] data) { + if (data.length == 2) { + ParserResult pr = loaded.getFirst(); + AuxCommandLine acl = new AuxCommandLine(data[0], pr.getDatabase()); + BibDatabase newBase = acl.perform(); + + boolean notSavedMsg = false; + + // write an output, if something could be resolved + if ((newBase != null) && newBase.hasEntries()) { + String subName = StringUtil.getCorrectFileName(data[1], "bib"); + saveDatabase(newBase, subName); + notSavedMsg = true; + } + + if (!notSavedMsg) { + System.out.println(Localization.lang("no library generated")); + } + return false; + } else { + return true; + } + } + + private void saveDatabase(BibDatabase newBase, String subName) { + try { + System.out.println(Localization.lang("Saving") + ": " + subName); + try (AtomicFileWriter fileWriter = new AtomicFileWriter(Path.of(subName), StandardCharsets.UTF_8)) { + BibWriter bibWriter = new BibWriter(fileWriter, OS.NEWLINE); + SelfContainedSaveConfiguration saveConfiguration = (SelfContainedSaveConfiguration) new SelfContainedSaveConfiguration() + .withReformatOnSave(cliPreferences.getLibraryPreferences().shouldAlwaysReformatOnSave()); + BibDatabaseWriter databaseWriter = new BibtexDatabaseWriter( + bibWriter, + saveConfiguration, + cliPreferences.getFieldPreferences(), + cliPreferences.getCitationKeyPatternPreferences(), + entryTypesManager); + databaseWriter.saveDatabase(new BibDatabaseContext(newBase)); + + // Show just a warning message if encoding did not work for all characters: + if (fileWriter.hasEncodingProblems()) { + System.err.println(Localization.lang("Warning") + ": " + + Localization.lang("UTF-8 could not be used to encode the following characters: %0", fileWriter.getEncodingProblems())); + } + } + } catch (IOException ex) { + System.err.println(Localization.lang("Could not save file.") + "\n" + ex.getLocalizedMessage()); + } + } + + private void exportFile(List loaded, String[] data) { + if (data.length == 1) { + // This signals that the latest import should be stored in BibTeX + // format to the given file. + if (!loaded.isEmpty()) { + ParserResult pr = loaded.getLast(); + if (!pr.isInvalid()) { + saveDatabase(pr.getDatabase(), data[0]); + } + } else { + System.err.println(Localization.lang("The output option depends on a valid import option.")); + } + } else if (data.length == 2) { + // This signals that the latest import should be stored in the given + // format to the given file. + ParserResult parserResult = loaded.getLast(); + + Path path = parserResult.getPath().get().toAbsolutePath(); + BibDatabaseContext databaseContext = parserResult.getDatabaseContext(); + databaseContext.setDatabasePath(path); + List fileDirForDatabase = databaseContext + .getFileDirectories(cliPreferences.getFilePreferences()); + System.out.println(Localization.lang("Exporting %0", data[0])); + ExporterFactory exporterFactory = ExporterFactory.create(cliPreferences); + Optional exporter = exporterFactory.getExporterByName(data[1]); + if (exporter.isEmpty()) { + System.err.println(Localization.lang("Unknown export format %0", data[1])); + } else { + // We have an exporter: + try { + exporter.get().export( + parserResult.getDatabaseContext(), + Path.of(data[0]), + parserResult.getDatabaseContext().getDatabase().getEntries(), + fileDirForDatabase, + Injector.instantiateModelOrService(JournalAbbreviationRepository.class)); + } catch (Exception ex) { + System.err.println(Localization.lang("Could not export file '%0' (reason: %1)", data[0], Throwables.getStackTraceAsString(ex))); + } + } + } + } + + private void importPreferences() { + try { + cliPreferences.importPreferences(Path.of(cli.getPreferencesImport())); + Injector.setModelOrService(BibEntryTypesManager.class, cliPreferences.getCustomEntryTypesRepository()); + } catch (JabRefException ex) { + LOGGER.error("Cannot import preferences", ex); + } + } + + private void resetPreferences(String value) { + if ("all".equals(value.trim())) { + try { + System.out.println(Localization.lang("Setting all preferences to default values.")); + cliPreferences.clear(); + new SharedDatabasePreferences().clear(); + } catch (BackingStoreException e) { + System.err.println(Localization.lang("Unable to clear preferences.")); + LOGGER.error("Unable to clear preferences", e); + } + } else { + String[] keys = value.split(","); + for (String key : keys) { + try { + cliPreferences.deleteKey(key.trim()); + System.out.println(Localization.lang("Resetting preference key '%0'", key.trim())); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + } + } + } + } + + private void regenerateCitationKeys(List loaded) { + for (ParserResult parserResult : loaded) { + BibDatabase database = parserResult.getDatabase(); + + LOGGER.info(Localization.lang("Regenerating citation keys according to metadata")); + + CitationKeyGenerator keyGenerator = new CitationKeyGenerator( + parserResult.getDatabaseContext(), + cliPreferences.getCitationKeyPatternPreferences()); + for (BibEntry entry : database.getEntries()) { + keyGenerator.generateAndSetKey(entry); + } + } + } + + /** + * Run an entry fetcher from the command line. + * + * @param fetchCommand A string containing both the name of the fetcher to use and the search query, separated by a : + * @return A parser result containing the entries fetched or null if an error occurred. + */ + private Optional fetch(String fetchCommand) { + if ((fetchCommand == null) || !fetchCommand.contains(":")) { + System.out.println(Localization.lang("Expected syntax for --fetch=':'")); + System.out.println(Localization.lang("The following fetchers are available:")); + return Optional.empty(); + } + + String[] split = fetchCommand.split(":"); + String engine = split[0]; + String query = split[1]; + + Set fetchers = WebFetchers.getSearchBasedFetchers( + cliPreferences.getImportFormatPreferences(), + cliPreferences.getImporterPreferences()); + Optional selectedFetcher = fetchers.stream() + .filter(fetcher -> fetcher.getName().equalsIgnoreCase(engine)) + .findFirst(); + if (selectedFetcher.isEmpty()) { + System.out.println(Localization.lang("Could not find fetcher '%0'", engine)); + + System.out.println(Localization.lang("The following fetchers are available:")); + fetchers.forEach(fetcher -> System.out.println(" " + fetcher.getName())); + + return Optional.empty(); + } else { + System.out.println(Localization.lang("Running query '%0' with fetcher '%1'.", query, engine)); + System.out.print(Localization.lang("Please wait...")); + try { + List matches = selectedFetcher.get().performSearch(query); + if (matches.isEmpty()) { + System.out.println("\r" + Localization.lang("No results found.")); + return Optional.empty(); + } else { + System.out.println("\r" + Localization.lang("Found %0 results.", String.valueOf(matches.size()))); + return Optional.of(new ParserResult(matches)); + } + } catch (FetcherException e) { + LOGGER.error("Error while fetching", e); + return Optional.empty(); + } + } + } + + public boolean shouldShutDown() { + return cli.isDisableGui() || cli.isShowVersion() || !guiNeeded; + } + + public List getUiCommands() { + return uiCommands; + } + + private void validateJournalAbbreviations() { + JournalAbbreviationRepository repository = Injector.instantiateModelOrService(JournalAbbreviationRepository.class); + List issues = repository.getValidationIssues(); + + if (issues.isEmpty()) { + System.out.println(Localization.lang("No validation issues found.")); + return; + } + + System.out.println(Localization.lang("Found %0 validation issues:", issues.size())); + for (ValidationResult issue : issues) { + System.out.printf("%s: %s%n", issue.getType(), issue.getMessage()); + } + } +} diff --git a/src/main/java/org/jabref/cli/CliOptions.java b/src/main/java/org/jabref/cli/CliOptions.java new file mode 100644 index 00000000000..027f98e0311 --- /dev/null +++ b/src/main/java/org/jabref/cli/CliOptions.java @@ -0,0 +1,419 @@ +package org.jabref.cli; + +import java.util.List; +import java.util.Objects; + +import javafx.util.Pair; + +import org.jabref.logic.exporter.ExporterFactory; +import org.jabref.logic.importer.ImportFormatReader; +import org.jabref.logic.l10n.Localization; +import org.jabref.logic.os.OS; +import org.jabref.logic.preferences.CliPreferences; +import org.jabref.logic.util.BuildInfo; +import org.jabref.model.strings.StringUtil; +import org.jabref.model.util.DummyFileUpdateMonitor; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; + +/** + * Holds the command line options. It parses it using Apache Commons CLI. + */ +public class CliOptions { + private static final int WIDTH = 100; // Number of characters per line before a line break must be added. + private static final String WRAPPED_LINE_PREFIX = ""; // If a line break is added, this prefix will be inserted at the beginning of the next line + private static final String STRING_TABLE_DELIMITER = " : "; + + private final CommandLine commandLine; + private final List leftOver; + + public CliOptions(String[] args) throws ParseException { + Options options = getOptions(); + this.commandLine = new DefaultParser().parse(options, args, true); + this.leftOver = commandLine.getArgList(); + } + + public static String getExportMatchesSyntax() { + return "[%s]searchTerm,outputFile:%s[,%s]".formatted( + Localization.lang("field"), + Localization.lang("file"), + Localization.lang("exportFormat")); + } + + public boolean isHelp() { + return commandLine.hasOption("help"); + } + + public boolean isShowVersion() { + return commandLine.hasOption("version"); + } + + public boolean isBlank() { + return commandLine.hasOption("blank"); + } + + public boolean isDisableGui() { + return commandLine.hasOption("nogui"); + } + + public boolean isCheckConsistency() { + return commandLine.hasOption("check-consistency"); + } + + public String getCheckConsistency() { + return commandLine.getOptionValue("check-consistency"); + } + + public String getCheckConsistencyOutputFormat() { + return commandLine.getOptionValue("output-format"); + } + + public boolean isPorcelainOutputMode() { + return commandLine.hasOption("porcelain"); + } + + public boolean isPreferencesExport() { + return commandLine.hasOption("prexp"); + } + + public String getPreferencesExport() { + return commandLine.getOptionValue("prexp", "jabref_prefs.xml"); + } + + public boolean isPreferencesImport() { + return commandLine.hasOption("primp"); + } + + public String getPreferencesImport() { + return commandLine.getOptionValue("primp", "jabref_prefs.xml"); + } + + public boolean isPreferencesReset() { + return commandLine.hasOption("prdef"); + } + + public String getPreferencesReset() { + return commandLine.getOptionValue("prdef"); + } + + public boolean isFileExport() { + return commandLine.hasOption("output"); + } + + public String getFileExport() { + return commandLine.getOptionValue("output"); + } + + public boolean isBibtexImport() { + return commandLine.hasOption("importBibtex"); + } + + public String getBibtexImport() { + return commandLine.getOptionValue("importBibtex"); + } + + public boolean isFileImport() { + return commandLine.hasOption("import"); + } + + public String getFileImport() { + return commandLine.getOptionValue("import"); + } + + public boolean isAuxImport() { + return commandLine.hasOption("aux"); + } + + public String getAuxImport() { + return commandLine.getOptionValue("aux"); + } + + public boolean isImportToOpenBase() { + return commandLine.hasOption("importToOpen"); + } + + public String getImportToOpenBase() { + return commandLine.getOptionValue("importToOpen"); + } + + public boolean isDebugLogging() { + return commandLine.hasOption("debug"); + } + + public boolean isFetcherEngine() { + return commandLine.hasOption("fetch"); + } + + public String getFetcherEngine() { + return commandLine.getOptionValue("fetch"); + } + + public boolean isExportMatches() { + return commandLine.hasOption("exportMatches"); + } + + public String getExportMatches() { + return commandLine.getOptionValue("exportMatches"); + } + + public boolean isGenerateCitationKeys() { + return commandLine.hasOption("generateCitationKeys"); + } + + public boolean isWriteXmpToPdf() { + return commandLine.hasOption("writeXmpToPdf"); + } + + public boolean isEmbedBibFileInPdf() { + return commandLine.hasOption("embedBibFileInPdf"); + } + + public boolean isWriteMetadataToPdf() { + return commandLine.hasOption("writeMetadataToPdf"); + } + + public String getWriteMetadataToPdf() { + return commandLine.hasOption("writeMetadatatoPdf") ? commandLine.getOptionValue("writeMetadataToPdf") : + commandLine.hasOption("writeXMPtoPdf") ? commandLine.getOptionValue("writeXmpToPdf") : + commandLine.hasOption("embeddBibfileInPdf") ? commandLine.getOptionValue("embeddBibfileInPdf") : null; + } + + public String getJumpToKey() { + return commandLine.getOptionValue("jumpToKey"); + } + + public boolean isJumpToKey() { + return commandLine.hasOption("jumpToKey"); + } + + public boolean isValidateJournals() { + return commandLine.hasOption("validate-journals"); + } + + public String getValidateJournals() { + return commandLine.getOptionValue("validate-journals"); + } + + private static Options getOptions() { + Options options = new Options(); + + // boolean options + options.addOption("h", "help", false, Localization.lang("Display help on command line options")); + options.addOption("n", "nogui", false, Localization.lang("No GUI. Only process command line options")); + options.addOption("g", "generateCitationKeys", false, Localization.lang("Regenerate all keys for the entries in a BibTeX file")); + options.addOption("b", "blank", false, Localization.lang("Do not open any files at startup")); + options.addOption("v", "version", false, Localization.lang("Display version")); + options.addOption(null, "debug", false, Localization.lang("Show debug level messages")); + + options.addOption(Option + .builder("i") + .longOpt("import") + .desc("%s: '%s'".formatted(Localization.lang("Import file"), "-i library.bib")) + .hasArg() + .argName("FILE[,FORMAT]") + .build()); + + options.addOption(Option + .builder() + .longOpt("importToOpen") + .desc(Localization.lang("Same as --import, but will be imported to the opened tab")) + .hasArg() + .argName("FILE[,FORMAT]") + .build()); + + options.addOption(Option + .builder("ib") + .longOpt("importBibtex") + .desc("%s: '%s'".formatted(Localization.lang("Import BibTeX"), "-ib @article{entry}")) + .hasArg() + .argName("BIBTEX_STRING") + .build()); + + options.addOption(Option + .builder("o") + .longOpt("output") + .desc("%s: '%s'".formatted(Localization.lang("Export an input to a file"), "-i db.bib -o db.htm,html")) + .hasArg() + .argName("FILE[,FORMAT]") + .build()); + + options.addOption(Option + .builder("m") + .longOpt("exportMatches") + .desc("%s: '%s'".formatted(Localization.lang("Matching"), "-i db.bib -m author=Newton,search.htm,html")) + .hasArg() + .argName("QUERY,FILE[,FORMAT]") + .build()); + + options.addOption(Option + .builder("f") + .longOpt("fetch") + .desc("%s: '%s'".formatted(Localization.lang("Run fetcher"), "-f Medline/PubMed:cancer")) + .hasArg() + .argName("FETCHER:QUERY") + .build()); + + options.addOption(Option + .builder("a") + .longOpt("aux") + .desc("%s: '%s'".formatted(Localization.lang("Sublibrary from AUX to BibTeX"), "-a thesis.aux,new.bib")) + .hasArg() + .argName("FILE[.aux],FILE[.bib] FILE") + .build()); + + options.addOption(Option + .builder("x") + .longOpt("prexp") + .desc("%s: '%s'".formatted(Localization.lang("Export preferences to a file"), "-x prefs.xml")) + .hasArg() + .argName("[FILE]") + .build()); + + options.addOption(Option + .builder("p") + .longOpt("primp") + .desc("%s: '%s'".formatted(Localization.lang("Import preferences from a file"), "-p prefs.xml")) + .hasArg() + .argName("[FILE]") + .build()); + + options.addOption(Option + .builder("d") + .longOpt("prdef") + .desc("%s: '%s'".formatted(Localization.lang("Reset preferences"), "-d mainFontSize,newline' or '-d all")) + .hasArg() + .argName("KEY1[,KEY2][,KEYn] | all") + .build()); + + options.addOption(Option + .builder() + .longOpt("writeXmpToPdf") + .desc("%s: '%s'".formatted(Localization.lang("Write BibTeX as XMP metadata to PDF."), "-w pathToMyOwnPaper.pdf")) + .hasArg() + .argName("CITEKEY1[,CITEKEY2][,CITEKEYn] | PDF1[,PDF2][,PDFn] | all") + .build()); + + options.addOption(Option + .builder() + .longOpt("embedBibFileInPdf") + .desc("%s: '%s'".formatted(Localization.lang("Embed BibTeX as attached file in PDF."), "-w pathToMyOwnPaper.pdf")) + .hasArg() + .argName("CITEKEY1[,CITEKEY2][,CITEKEYn] | PDF1[,PDF2][,PDFn] | all") + .build()); + + options.addOption(Option + .builder("w") + .longOpt("writeMetadataToPdf") + .desc("%s: '%s'".formatted(Localization.lang("Write BibTeX to PDF (XMP and embedded)"), "-w pathToMyOwnPaper.pdf")) + .hasArg() + .argName("CITEKEY1[,CITEKEY2][,CITEKEYn] | PDF1[,PDF2][,PDFn] | all") + .build()); + + options.addOption(Option + .builder("j") + .longOpt("jumpToKey") + .desc("%s: '%s'".formatted(Localization.lang("Jump to the entry of the given citation key."), "-j key")) + .hasArg() + .argName("CITATIONKEY") + .build()); + + options.addOption(Option + .builder("cc") + .longOpt("check-consistency") + .desc(Localization.lang("Check consistency of BibTeX file")) + .hasArg() + .argName("FILE") + .build()); + + options.addOption(Option + .builder() + .longOpt("output-format") + .desc(Localization.lang("Output format for consistency check (txt/csv)")) + .hasArg() + .argName("FORMAT") + .build()); + + options.addOption(Option + .builder("porcelain") + .longOpt("porcelain") + .desc(Localization.lang("Script-friendly output")) + .build()); + + options.addOption(Option + .builder() + .longOpt("validate-journals") + .desc(Localization.lang("Validate journal abbreviation files")) + .build()); + + return options; + } + + public void displayVersion() { + System.out.println(getVersionInfo()); + } + + public static void printUsage(CliPreferences preferences) { + String header = ""; + + ImportFormatReader importFormatReader = new ImportFormatReader( + preferences.getImporterPreferences(), + preferences.getImportFormatPreferences(), + preferences.getCitationKeyPatternPreferences(), + new DummyFileUpdateMonitor() + ); + List> importFormats = importFormatReader + .getImportFormats().stream() + .map(format -> new Pair<>(format.getName(), format.getId())) + .toList(); + String importFormatsIntro = Localization.lang("Available import formats"); + String importFormatsList = "%s:%n%s%n".formatted(importFormatsIntro, alignStringTable(importFormats)); + + ExporterFactory exporterFactory = ExporterFactory.create(preferences); + List> exportFormats = exporterFactory + .getExporters().stream() + .map(format -> new Pair<>(format.getName(), format.getId())) + .toList(); + String outFormatsIntro = Localization.lang("Available export formats"); + String outFormatsList = "%s:%n%s%n".formatted(outFormatsIntro, alignStringTable(exportFormats)); + + String footer = '\n' + importFormatsList + outFormatsList + "\nPlease report issues at https://github.com/JabRef/jabref/issues."; + + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp(WIDTH, "jabref [OPTIONS] [BIBTEX_FILE]\n\nOptions:", header, getOptions(), footer, true); + } + + private String getVersionInfo() { + return "JabRef %s".formatted(new BuildInfo().version); + } + + public List getLeftOver() { + return leftOver; + } + + protected static String alignStringTable(List> table) { + StringBuilder sb = new StringBuilder(); + + int maxLength = table.stream() + .mapToInt(pair -> Objects.requireNonNullElse(pair.getKey(), "").length()) + .max().orElse(0); + + for (Pair pair : table) { + int padding = Math.max(0, maxLength - pair.getKey().length()); + sb.append(WRAPPED_LINE_PREFIX); + sb.append(pair.getKey()); + + sb.append(StringUtil.repeatSpaces(padding)); + + sb.append(STRING_TABLE_DELIMITER); + sb.append(pair.getValue()); + sb.append(OS.NEWLINE); + } + + return sb.toString(); + } +} From 0bda42ef212e0ecee0bb5aaf691893bd1348449e Mon Sep 17 00:00:00 2001 From: Aspasia Tsagkaropoulou Date: Wed, 4 Jun 2025 01:40:24 +0300 Subject: [PATCH 30/35] Add real time validation in GUI --- .../journals/JournalAbbreviationsTab.java | 48 +++++++++++++++++++ .../JournalAbbreviationsTabViewModel.java | 21 ++++++++ 2 files changed, 69 insertions(+) diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTab.java b/jabgui/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTab.java index 5bfa504edf5..f7b2ebb58f6 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTab.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTab.java @@ -36,6 +36,9 @@ import jakarta.inject.Inject; import org.controlsfx.control.textfield.CustomTextField; +import java.util.List; +import java.util.stream.Collectors; + /** * This class controls the user interface of the journal abbreviations dialog. The UI elements and their layout are * defined in the FXML file. @@ -59,6 +62,7 @@ public class JournalAbbreviationsTab extends AbstractPreferenceTabView cellData.getValue().nameProperty()); journalTableNameColumn.setCellFactory(TextFieldTableCell.forTableColumn()); + journalTableNameColumn.setOnEditCommit(event -> { + if (viewModel.validateAbbreviationsProperty().get()) { + AbbreviationViewModel item = event.getRowValue(); + String newValue = event.getNewValue(); + List results = viewModel.validateAbbreviation(item.getName(), newValue, item.getAbbreviation()); + if (!results.isEmpty()) { + event.consume(); + dialogService.showErrorDialogAndWait(Localization.lang("Validation Error"), + results.stream() + .map(ValidationResult::getMessage) + .collect(Collectors.joining("\n"))); + } + } + }); journalTableAbbreviationColumn.setCellValueFactory(cellData -> cellData.getValue().abbreviationProperty()); journalTableAbbreviationColumn.setCellFactory(TextFieldTableCell.forTableColumn()); + journalTableAbbreviationColumn.setOnEditCommit(event -> { + if (viewModel.validateAbbreviationsProperty().get()) { + AbbreviationViewModel item = event.getRowValue(); + String newValue = event.getNewValue(); + List results = viewModel.validateAbbreviation(item.getName(), item.getAbbreviation(), newValue); + if (!results.isEmpty()) { + event.consume(); + dialogService.showErrorDialogAndWait(Localization.lang("Validation Error"), + results.stream() + .map(ValidationResult::getMessage) + .collect(Collectors.joining("\n"))); + } + } + }); journalTableShortestUniqueAbbreviationColumn.setCellValueFactory(cellData -> cellData.getValue().shortestUniqueAbbreviationProperty()); journalTableShortestUniqueAbbreviationColumn.setCellFactory(TextFieldTableCell.forTableColumn()); + journalTableShortestUniqueAbbreviationColumn.setOnEditCommit(event -> { + if (viewModel.validateAbbreviationsProperty().get()) { + AbbreviationViewModel item = event.getRowValue(); + String newValue = event.getNewValue(); + List results = viewModel.validateAbbreviation(item.getName(), item.getAbbreviation(), newValue); + if (!results.isEmpty()) { + event.consume(); + dialogService.showErrorDialogAndWait(Localization.lang("Validation Error"), + results.stream() + .map(ValidationResult::getMessage) + .collect(Collectors.joining("\n"))); + } + } + }); actionsColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty()); new ValueTableCellFactory() diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTabViewModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTabViewModel.java index 7b95ac28244..d97a5d5c015 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTabViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTabViewModel.java @@ -49,6 +49,7 @@ public class JournalAbbreviationsTabViewModel implements PreferenceTabViewModel private final SimpleBooleanProperty isEditableAndRemovable = new SimpleBooleanProperty(false); private final SimpleBooleanProperty isAbbreviationEditableAndRemovable = new SimpleBooleanProperty(false); private final SimpleBooleanProperty useFJournal = new SimpleBooleanProperty(true); + private final SimpleBooleanProperty validateAbbreviations = new SimpleBooleanProperty(true); private final DialogService dialogService; private final TaskExecutor taskExecutor; @@ -66,6 +67,9 @@ public JournalAbbreviationsTabViewModel(JournalAbbreviationPreferences abbreviat this.journalAbbreviationRepository = Objects.requireNonNull(journalAbbreviationRepository); this.abbreviationsPreferences = abbreviationsPreferences; + useFJournal.setValue(abbreviationsPreferences.shouldUseFJournalField()); + validateAbbreviations.setValue(abbreviationsPreferences.shouldValidateAbbreviations()); + abbreviationsCount.bind(abbreviations.sizeProperty()); currentAbbreviation.addListener((observable, oldValue, newValue) -> { boolean isAbbreviation = (newValue != null) && !newValue.isPseudoAbbreviation(); @@ -103,6 +107,10 @@ public JournalAbbreviationsTabViewModel(JournalAbbreviationPreferences abbreviat } } }); + + // Bind preferences + useFJournal.addListener((obs, oldValue, newValue) -> abbreviationsPreferences.setUseFJournalField(newValue)); + validateAbbreviations.addListener((obs, oldValue, newValue) -> abbreviationsPreferences.setValidateAbbreviations(newValue)); } @Override @@ -387,4 +395,17 @@ public SimpleBooleanProperty isFileRemovableProperty() { public SimpleBooleanProperty useFJournalProperty() { return useFJournal; } + + public BooleanProperty validateAbbreviationsProperty() { + return validateAbbreviations; + } + + public void setValidateAbbreviations(boolean validateAbbreviations) { + this.validateAbbreviations.set(validateAbbreviations); + } + + public List validateAbbreviation(String name, String abbreviation, String shortestUniqueAbbreviation) { + Abbreviation abbreviationObject = new Abbreviation(name, abbreviation, shortestUniqueAbbreviation); + return journalAbbreviationRepository.getValidationIssues(); + } } From b5b20a2f4948fa3bc6056692adcff2998aea3d65 Mon Sep 17 00:00:00 2001 From: Aspasia Tsagkaropoulou Date: Wed, 4 Jun 2025 01:48:47 +0300 Subject: [PATCH 31/35] Localize message using Localization.lang --- .../java/org/jabref/cli/ArgumentProcessor.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/jabref/cli/ArgumentProcessor.java b/src/main/java/org/jabref/cli/ArgumentProcessor.java index da8db97e300..c0702c474d9 100644 --- a/src/main/java/org/jabref/cli/ArgumentProcessor.java +++ b/src/main/java/org/jabref/cli/ArgumentProcessor.java @@ -870,17 +870,16 @@ public List getUiCommands() { } private void validateJournalAbbreviations() { - JournalAbbreviationRepository repository = Injector.instantiateModelOrService(JournalAbbreviationRepository.class); + JournalAbbreviationRepository repository = JournalAbbreviationLoader.loadRepository(preferences.getJournalAbbreviationPreferences()); List issues = repository.getValidationIssues(); if (issues.isEmpty()) { - System.out.println(Localization.lang("No validation issues found.")); - return; - } - - System.out.println(Localization.lang("Found %0 validation issues:", issues.size())); - for (ValidationResult issue : issues) { - System.out.printf("%s: %s%n", issue.getType(), issue.getMessage()); + System.out.println(Localization.lang("No validation issues found in journal abbreviations.")); + } else { + System.out.println(Localization.lang("Found %0 validation issues:", issues.size())); + for (ValidationResult issue : issues) { + System.out.println(Localization.lang("Type: %0, Message: %1", issue.getType(), issue.getMessage())); + } } } } From 8f1f017543f811cdb3867787dcf5ed3acfa77d9e Mon Sep 17 00:00:00 2001 From: Aspasia Tsagkaropoulou Date: Wed, 4 Jun 2025 01:51:17 +0300 Subject: [PATCH 32/35] Modify getValidateJournals to use java.util.Optional --- src/main/java/org/jabref/cli/CliOptions.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/jabref/cli/CliOptions.java b/src/main/java/org/jabref/cli/CliOptions.java index 027f98e0311..0a2c9dd8785 100644 --- a/src/main/java/org/jabref/cli/CliOptions.java +++ b/src/main/java/org/jabref/cli/CliOptions.java @@ -2,6 +2,7 @@ import java.util.List; import java.util.Objects; +import java.util.Optional; import javafx.util.Pair; @@ -191,12 +192,8 @@ public boolean isJumpToKey() { return commandLine.hasOption("jumpToKey"); } - public boolean isValidateJournals() { - return commandLine.hasOption("validate-journals"); - } - - public String getValidateJournals() { - return commandLine.getOptionValue("validate-journals"); + public Optional getValidateJournals() { + return Optional.ofNullable(commandLine.getOptionValue("validate-journals")); } private static Options getOptions() { From 3c285e7a82dce18a84e8e51833ecf5aecb7589b1 Mon Sep 17 00:00:00 2001 From: Aspasia Tsagkaropoulou Date: Wed, 4 Jun 2025 22:27:28 +0300 Subject: [PATCH 33/35] Resolve Checkstyle errors --- .../org/jabref/cli/ArgumentProcessor.java | 25 +++++++++++++------ src/main/java/org/jabref/cli/CliOptions.java | 4 +++ 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/jabref/cli/ArgumentProcessor.java b/src/main/java/org/jabref/cli/ArgumentProcessor.java index c0702c474d9..e4b4b59a00b 100644 --- a/src/main/java/org/jabref/cli/ArgumentProcessor.java +++ b/src/main/java/org/jabref/cli/ArgumentProcessor.java @@ -39,7 +39,9 @@ import org.jabref.logic.importer.SearchBasedFetcher; import org.jabref.logic.importer.WebFetchers; import org.jabref.logic.importer.fileformat.BibtexParser; +import org.jabref.logic.journals.JournalAbbreviationLoader; import org.jabref.logic.journals.JournalAbbreviationRepository; +import org.jabref.logic.journals.JournalAbbreviationValidator; import org.jabref.logic.l10n.Localization; import org.jabref.logic.net.URLDownload; import org.jabref.logic.os.OS; @@ -870,15 +872,24 @@ public List getUiCommands() { } private void validateJournalAbbreviations() { - JournalAbbreviationRepository repository = JournalAbbreviationLoader.loadRepository(preferences.getJournalAbbreviationPreferences()); - List issues = repository.getValidationIssues(); - + JournalAbbreviationRepository repository = loadJournalAbbreviationRepository(); + List issues = repository.getValidationIssues(); + printValidationResults(issues); + } + + private JournalAbbreviationRepository loadJournalAbbreviationRepository() { + return JournalAbbreviationLoader.loadRepository(cliPreferences.getJournalAbbreviationPreferences()); + } + + private void printValidationResults(List issues) { if (issues.isEmpty()) { - System.out.println(Localization.lang("No validation issues found in journal abbreviations.")); + System.out.println(Localization.lang("No validation issues found in journal abbreviations")); } else { - System.out.println(Localization.lang("Found %0 validation issues:", issues.size())); - for (ValidationResult issue : issues) { - System.out.println(Localization.lang("Type: %0, Message: %1", issue.getType(), issue.getMessage())); + System.out.println(Localization.lang("Found %0 validation issues", issues.size())); + for (JournalAbbreviationValidator.ValidationResult issue : issues) { + System.out.println(Localization.lang("Type: %0, Message: %1", + issue.getType().name(), + issue.getMessage())); } } } diff --git a/src/main/java/org/jabref/cli/CliOptions.java b/src/main/java/org/jabref/cli/CliOptions.java index 0a2c9dd8785..9c380749238 100644 --- a/src/main/java/org/jabref/cli/CliOptions.java +++ b/src/main/java/org/jabref/cli/CliOptions.java @@ -192,6 +192,10 @@ public boolean isJumpToKey() { return commandLine.hasOption("jumpToKey"); } + public boolean isValidateJournals() { + return commandLine.hasOption("validate-journals"); + } + public Optional getValidateJournals() { return Optional.ofNullable(commandLine.getOptionValue("validate-journals")); } From e33db01ce29f49d6aa4c147eeffb52ed39d0d15b Mon Sep 17 00:00:00 2001 From: Aspasia Tsagkaropoulou Date: Thu, 5 Jun 2025 00:29:28 +0300 Subject: [PATCH 34/35] Move validator back to original location --- .../org/jabref/logic/journals/JournalAbbreviationValidator.java | 0 .../jabref/logic/journals/JournalAbbreviationValidatorTest.java | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {jablib/src => src}/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java (100%) rename {jablib/src => src}/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java (100%) diff --git a/jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java b/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java similarity index 100% rename from jablib/src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java rename to src/main/java/org/jabref/logic/journals/JournalAbbreviationValidator.java diff --git a/jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java b/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java similarity index 100% rename from jablib/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java rename to src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java From 9ddd8c48a56c9b5b6dafdb5143769343d7083c2a Mon Sep 17 00:00:00 2001 From: Aspasia Tsagkaropoulou Date: Thu, 5 Jun 2025 13:14:44 +0300 Subject: [PATCH 35/35] Change assertFalse and assertTrue to assertEquals --- .../JournalAbbreviationValidatorTest.java | 106 ++++++++---------- 1 file changed, 47 insertions(+), 59 deletions(-) diff --git a/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java b/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java index 1ac966c87dc..20a7b59f08d 100644 --- a/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java +++ b/src/test/java/org/jabref/logic/journals/JournalAbbreviationValidatorTest.java @@ -22,7 +22,7 @@ void checkWrongEscapeWithInvalidFullName() { String abbreviation = "Problemy Mat."; var result = validator.checkWrongEscape(fullName, abbreviation, 1).orElseThrow(); - assertFalse(result.isValid()); + assertEquals(false, result.isValid()); assertEquals("Invalid escape sequence in full name at position 15", result.getMessage()); assertEquals("Use valid escape sequences: \\\\, \\\", \\n, \\t, \\r, \\b, \\f, \\uXXXX", result.getSuggestion()); assertEquals(1, result.getLineNumber()); @@ -34,7 +34,7 @@ void checkWrongEscapeWithInvalidAbbreviation() { String abbreviation = "J. Evol. Biochem. Physiol.\\"; var result = validator.checkWrongEscape(fullName, abbreviation, 2).orElseThrow(); - assertFalse(result.isValid()); + assertEquals(false, result.isValid()); assertEquals("Invalid escape sequence in abbreviation at position 22", result.getMessage()); assertEquals("Use valid escape sequences: \\\\, \\\", \\n, \\t, \\r, \\b, \\f, \\uXXXX", result.getSuggestion()); assertEquals(2, result.getLineNumber()); @@ -46,7 +46,7 @@ void checkWrongEscapeWithValidEscapes() { String abbreviation = "J. with \\r return and \\b backspace"; var result = validator.checkWrongEscape(fullName, abbreviation, 1).orElseThrow(); - assertTrue(result.isValid()); + assertEquals(true, result.isValid()); assertEquals("", result.getMessage()); assertEquals("", result.getSuggestion()); } @@ -57,9 +57,9 @@ void checkWrongEscapeWithMultipleIssues() { String abbreviation = "J. with \\y invalid"; var result = validator.checkWrongEscape(fullName, abbreviation, 1).orElseThrow(); - assertFalse(result.isValid()); - assertTrue(result.getMessage().startsWith("Invalid escape sequence")); - assertTrue(result.getSuggestion().contains("valid escape sequences")); + assertEquals(false, result.isValid()); + assertEquals("Invalid escape sequence in full name at position 13", result.getMessage()); + assertEquals("Use valid escape sequences: \\\\, \\\", \\n, \\t, \\r, \\b, \\f, \\uXXXX", result.getSuggestion()); } @Test @@ -68,7 +68,7 @@ void checkNonUtf8WithValidInput() { String abbreviation = "J. Phys."; var result = validator.checkNonUtf8(fullName, abbreviation, 1).orElseThrow(); - assertTrue(result.isValid()); + assertEquals(true, result.isValid()); assertEquals("", result.getMessage()); assertEquals("", result.getSuggestion()); } @@ -80,7 +80,7 @@ void checkNonUtf8WithInvalidInput() { String abbreviation = "J. Phys."; var result = validator.checkNonUtf8(fullName, abbreviation, 1).orElseThrow(); - assertFalse(result.isValid()); + assertEquals(false, result.isValid()); assertEquals("Journal name or abbreviation contains invalid UTF-8 sequences", result.getMessage()); assertEquals("Ensure all characters are valid UTF-8. Remove or replace any invalid characters.", result.getSuggestion()); } @@ -91,7 +91,7 @@ void checkStartingLettersWithValidInput() { String abbreviation = "J. Phys."; var result = validator.checkStartingLetters(fullName, abbreviation, 1).orElseThrow(); - assertTrue(result.isValid()); + assertEquals(true, result.isValid()); assertEquals("", result.getMessage()); assertEquals("", result.getSuggestion()); } @@ -102,7 +102,7 @@ void checkStartingLettersWithInvalidInput() { String abbreviation = "Phys. J."; var result = validator.checkStartingLetters(fullName, abbreviation, 1).orElseThrow(); - assertFalse(result.isValid()); + assertEquals(false, result.isValid()); assertEquals("Abbreviation does not begin with same letter as full journal name", result.getMessage()); assertEquals("Should start with 'j' (from 'Journal')", result.getSuggestion()); } @@ -113,7 +113,7 @@ void checkStartingLettersWithThePrefix() { String abbreviation = "J. Phys."; var result = validator.checkStartingLetters(fullName, abbreviation, 1).orElseThrow(); - assertTrue(result.isValid()); + assertEquals(true, result.isValid()); assertEquals("", result.getMessage()); assertEquals("", result.getSuggestion()); } @@ -124,7 +124,7 @@ void checkStartingLettersWithAPrefix() { String abbreviation = "J. Phys."; var result = validator.checkStartingLetters(fullName, abbreviation, 1).orElseThrow(); - assertTrue(result.isValid()); + assertEquals(true, result.isValid()); assertEquals("", result.getMessage()); assertEquals("", result.getSuggestion()); } @@ -135,7 +135,7 @@ void checkAbbreviationEqualsFullTextWithValidInput() { String abbreviation = "J. Phys."; var result = validator.checkAbbreviationEqualsFullText(fullName, abbreviation, 1).orElseThrow(); - assertTrue(result.isValid()); + assertEquals(true, result.isValid()); assertEquals("", result.getMessage()); assertEquals("", result.getSuggestion()); } @@ -146,7 +146,7 @@ void checkAbbreviationEqualsFullTextWithInvalidInput() { String abbreviation = "Quantum"; var result = validator.checkAbbreviationEqualsFullText(fullName, abbreviation, 1).orElseThrow(); - assertFalse(result.isValid()); + assertEquals(false, result.isValid()); assertEquals("Abbreviation is the same as the full text", result.getMessage()); assertEquals("Consider using a shorter abbreviation to distinguish it from the full name", result.getSuggestion()); } @@ -157,7 +157,7 @@ void checkOutdatedManagementAbbreviationWithValidInput() { String abbreviation = "Manag. Sci."; var result = validator.checkOutdatedManagementAbbreviation(fullName, abbreviation, 1).orElseThrow(); - assertTrue(result.isValid()); + assertEquals(true, result.isValid()); assertEquals("", result.getMessage()); assertEquals("", result.getSuggestion()); } @@ -168,7 +168,7 @@ void checkOutdatedManagementAbbreviationWithInvalidInput() { String abbreviation = "Manage. Sci."; var result = validator.checkOutdatedManagementAbbreviation(fullName, abbreviation, 1).orElseThrow(); - assertFalse(result.isValid()); + assertEquals(false, result.isValid()); assertEquals("Management is abbreviated with outdated \"Manage.\" instead of \"Manag.\"", result.getMessage()); assertEquals("Update to use the standard abbreviation \"Manag.\"", result.getSuggestion()); } @@ -181,11 +181,9 @@ void checkDuplicateFullNames() { validator.validate("Journal of Physics", "J. Phys. A", 2); var issues = validator.getIssues(); - assertFalse(issues.isEmpty()); - assertTrue(issues.stream() - .anyMatch(issue -> issue.getMessage().contains("Duplicate full name"))); - assertTrue(issues.stream() - .anyMatch(issue -> issue.getSuggestion().contains("consolidating abbreviations"))); + assertEquals(1, issues.size()); + assertEquals("Duplicate full name 'Journal of Physics' with different abbreviations: J. Phys., J. Phys. A", issues.get(0).getMessage()); + assertEquals("Consider consolidating abbreviations or using more specific full names", issues.get(0).getSuggestion()); } @Test @@ -196,11 +194,9 @@ void checkDuplicateAbbreviations() { validator.validate("Journal of Physiology", "J. Phys.", 2); var issues = validator.getIssues(); - assertFalse(issues.isEmpty()); - assertTrue(issues.stream() - .anyMatch(issue -> issue.getMessage().contains("Duplicate abbreviation"))); - assertTrue(issues.stream() - .anyMatch(issue -> issue.getSuggestion().contains("more specific abbreviations"))); + assertEquals(1, issues.size()); + assertEquals("Duplicate abbreviation 'J. Phys.' used for different journals: Journal of Physics; Journal of Physiology", issues.get(0).getMessage()); + assertEquals("Consider using more specific abbreviations to avoid ambiguity", issues.get(0).getSuggestion()); } @Test @@ -209,7 +205,7 @@ void checkWrongEscapeWithMultipleValidEscapes() { String abbreviation = "J. with \\n\\t\\r\\b\\f\\\"\\\\"; var result = validator.checkWrongEscape(fullName, abbreviation, 1).orElseThrow(); - assertTrue(result.isValid()); + assertEquals(true, result.isValid()); assertEquals("", result.getMessage()); assertEquals("", result.getSuggestion()); } @@ -220,7 +216,7 @@ void checkWrongEscapeWithUnicodeEscape() { String abbreviation = "J. with \\u0042"; var result = validator.checkWrongEscape(fullName, abbreviation, 1).orElseThrow(); - assertTrue(result.isValid()); + assertEquals(true, result.isValid()); assertEquals("", result.getMessage()); assertEquals("", result.getSuggestion()); } @@ -231,9 +227,9 @@ void checkWrongEscapeWithInvalidUnicodeEscape() { String abbreviation = "J. Phys."; var result = validator.checkWrongEscape(fullName, abbreviation, 1).orElseThrow(); - assertFalse(result.isValid()); + assertEquals(false, result.isValid()); assertEquals("Invalid escape sequence in full name at position 13", result.getMessage()); - assertTrue(result.getSuggestion().contains("valid escape sequences")); + assertEquals("Use valid escape sequences: \\\\, \\\", \\n, \\t, \\r, \\b, \\f, \\uXXXX", result.getSuggestion()); } @Test @@ -242,9 +238,9 @@ void checkNonUtf8WithMultipleInvalidCharacters() { String abbreviation = "J. Phys.\uFFFD"; var result = validator.checkNonUtf8(fullName, abbreviation, 1).orElseThrow(); - assertFalse(result.isValid()); + assertEquals(false, result.isValid()); assertEquals("Journal name or abbreviation contains invalid UTF-8 sequences", result.getMessage()); - assertTrue(result.getSuggestion().contains("valid UTF-8")); + assertEquals("Ensure all characters are valid UTF-8. Remove or replace any invalid characters.", result.getSuggestion()); } @Test @@ -253,7 +249,7 @@ void checkStartingLettersWithMultiplePrefixes() { String abbreviation = "J. Phys."; var result = validator.checkStartingLetters(fullName, abbreviation, 1).orElseThrow(); - assertTrue(result.isValid()); + assertEquals(true, result.isValid()); assertEquals("", result.getMessage()); assertEquals("", result.getSuggestion()); } @@ -264,7 +260,7 @@ void checkStartingLettersWithSpecialCharacters() { String abbreviation = "J. Phys. Chem."; var result = validator.checkStartingLetters(fullName, abbreviation, 1).orElseThrow(); - assertTrue(result.isValid()); + assertEquals(true, result.isValid()); assertEquals("", result.getMessage()); assertEquals("", result.getSuggestion()); } @@ -275,7 +271,7 @@ void checkStartingLettersWithNumbers() { String abbreviation = "2D Mater."; var result = validator.checkStartingLetters(fullName, abbreviation, 1).orElseThrow(); - assertTrue(result.isValid()); + assertEquals(true, result.isValid()); assertEquals("", result.getMessage()); assertEquals("", result.getSuggestion()); } @@ -283,15 +279,15 @@ void checkStartingLettersWithNumbers() { @Test void validateWithEmptyInputs() { var results = validator.validate("", "", 1); - assertFalse(results.isEmpty()); - assertTrue(results.stream().anyMatch(r -> !r.isValid())); + assertEquals(5, results.size()); + assertEquals(false, results.get(0).isValid()); } @Test void validateWithWhitespaceOnly() { var results = validator.validate(" ", " ", 1); - assertFalse(results.isEmpty()); - assertTrue(results.stream().anyMatch(r -> !r.isValid())); + assertEquals(5, results.size()); + assertEquals(false, results.get(0).isValid()); } @Test @@ -300,9 +296,8 @@ void checkDuplicateFullNamesWithCaseInsensitivity() { validator.validate("JOURNAL OF PHYSICS", "J. Phys.", 2); var issues = validator.getIssues(); - assertFalse(issues.isEmpty()); - assertTrue(issues.stream() - .anyMatch(issue -> issue.getMessage().contains("Duplicate full name"))); + assertEquals(1, issues.size()); + assertEquals("Duplicate full name 'JOURNAL OF PHYSICS' with different abbreviations: J. Phys., J. Phys.", issues.get(0).getMessage()); } @Test @@ -311,9 +306,8 @@ void checkDuplicateAbbreviationsWithCaseInsensitivity() { validator.validate("Journal of Physiology", "j. phys.", 2); var issues = validator.getIssues(); - assertFalse(issues.isEmpty()); - assertTrue(issues.stream() - .anyMatch(issue -> issue.getMessage().contains("Duplicate abbreviation"))); + assertEquals(1, issues.size()); + assertEquals("Duplicate abbreviation 'j. phys.' used for different journals: Journal of Physics; Journal of Physiology", issues.get(0).getMessage()); } @Test @@ -322,9 +316,9 @@ void checkAbbreviationEqualsFullTextWithSpecialCharacters() { String abbreviation = "Physics & Chemistry"; var result = validator.checkAbbreviationEqualsFullText(fullName, abbreviation, 1).orElseThrow(); - assertFalse(result.isValid()); + assertEquals(false, result.isValid()); assertEquals("Abbreviation is the same as the full text", result.getMessage()); - assertTrue(result.getSuggestion().contains("shorter abbreviation")); + assertEquals("Consider using a shorter abbreviation to distinguish it from the full name", result.getSuggestion()); } @Test @@ -338,9 +332,9 @@ void checkOutdatedManagementAbbreviationWithVariations() { for (String abbreviation : invalidAbbreviations) { var result = validator.checkOutdatedManagementAbbreviation("Management Science", abbreviation, 1).orElseThrow(); - assertFalse(result.isValid()); - assertTrue(result.getMessage().contains("outdated")); - assertTrue(result.getSuggestion().contains("Manag.")); + assertEquals(false, result.isValid()); + assertEquals("Management is abbreviated with outdated \"Manage.\" instead of \"Manag.\"", result.getMessage()); + assertEquals("Update to use the standard abbreviation \"Manag.\"", result.getSuggestion()); } } @@ -350,9 +344,8 @@ void validateWithVeryLongInputs() { String longAbbr = "B".repeat(1000); var results = validator.validate(longName, longAbbr, 1); - assertFalse(results.isEmpty()); - // Should still perform all validations without throwing exceptions assertEquals(5, results.size()); + assertEquals(false, results.get(0).isValid()); } @Test @@ -361,14 +354,9 @@ void validateWithAllChecks() { String abbreviation = "Problemy Mat."; var results = validator.validate(fullName, abbreviation, 1); - assertFalse(results.isEmpty()); - - // Should have 5 results (3 error checks + 2 warning checks) assertEquals(5, results.size()); - - // First result should be the wrong escape check - assertFalse(results.get(0).isValid()); + assertEquals(false, results.get(0).isValid()); assertEquals("Invalid escape sequence in full name at position 15", results.get(0).getMessage()); - assertTrue(results.get(0).getSuggestion().contains("valid escape sequences")); + assertEquals("Use valid escape sequences: \\\\, \\\", \\n, \\t, \\r, \\b, \\f, \\uXXXX", results.get(0).getSuggestion()); } }