From 812fa4b982b874fae413e8502bf6f8ff662218d9 Mon Sep 17 00:00:00 2001 From: Philip <37398281+palukku@users.noreply.github.com> Date: Fri, 5 Sep 2025 21:28:42 +0200 Subject: [PATCH 01/25] extend CheckIntegrity --- .../java/org/jabref/cli/CheckIntegrity.java | 125 ++++++++++++++++-- 1 file changed, 115 insertions(+), 10 deletions(-) diff --git a/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java b/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java index 8f4bef85c42..3ef47772d0f 100644 --- a/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java +++ b/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java @@ -1,8 +1,18 @@ package org.jabref.cli; -import java.io.File; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.Callable; +import org.jabref.logic.importer.ParserResult; +import org.jabref.logic.integrity.IntegrityCheck; +import org.jabref.logic.integrity.IntegrityMessage; +import org.jabref.logic.journals.JournalAbbreviationLoader; import org.jabref.logic.l10n.Localization; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; + +import picocli.CommandLine; import static picocli.CommandLine.Command; import static picocli.CommandLine.Mixin; @@ -10,27 +20,122 @@ import static picocli.CommandLine.Parameters; @Command(name = "check-integrity", description = "Check integrity of the database.") -class CheckIntegrity implements Runnable { +class CheckIntegrity implements Callable { + + @CommandLine.ParentCommand + private ArgumentProcessor argumentProcessor; @Mixin private ArgumentProcessor.SharedOptions sharedOptions = new ArgumentProcessor.SharedOptions(); - @Parameters(index = "0", description = "BibTeX file to check", arity = "0..1") - private File inputFile; + @Parameters(description = "BibTeX file to check", arity = "1") + private String inputFile; - @Option(names = {"--input"}, description = "Input BibTeX file") - private File inputOption; + @Option(names = {"--output-format"}, description = "Output format: errorformat, txt or csv", defaultValue = "errorformat") + private String outputFormat; - @Option(names = {"--output-format"}, description = "Output format: txt or csv") - private String outputFormat = "txt"; // FixMe: Default value? + @Option(names = {"--allow-integer-edition"}, description = "Allows Integer edition: true or false", defaultValue = "true") + private boolean allowIntegerEdition = true; @Override - public void run() { + public Integer call() { + Optional parserResult = ArgumentProcessor.importFile( + inputFile, + "bibtex", + argumentProcessor.cliPreferences, + sharedOptions.porcelain); + if (parserResult.isEmpty()) { + System.out.println(Localization.lang("Unable to open file '%0'.", inputFile)); + return 2; + } + + if (parserResult.get().isInvalid()) { + System.out.println(Localization.lang("Input file '%0' is invalid and could not be parsed.", inputFile)); + return 2; + } + if (!sharedOptions.porcelain) { System.out.println(Localization.lang("Checking integrity of '%0'.", inputFile)); System.out.flush(); } - // TODO: Implement integrity checking + BibDatabaseContext databaseContext = parserResult.get().getDatabaseContext(); + + IntegrityCheck integrityCheck = new IntegrityCheck( + databaseContext, + argumentProcessor.cliPreferences.getFilePreferences(), + argumentProcessor.cliPreferences.getCitationKeyPatternPreferences(), + JournalAbbreviationLoader.loadRepository(argumentProcessor.cliPreferences.getJournalAbbreviationPreferences()), + allowIntegerEdition + ); + + List messages = databaseContext.getEntries().stream() + .flatMap(entry -> integrityCheck.checkEntry(entry).stream()) + .toList(); + + switch (outputFormat) { + case "errorformat" -> messages.forEach(message -> { + if (message.field() != null) { + System.out.println(String.format("%s:%d: %s: %s", + inputFile, + message.getLineNumber().orElse(0), + message.field().getName(), + message.message())); + } else { + System.out.println(String.format("%s:%d: %s", + inputFile, + message.getLineNumber().orElse(0), + message.message())); + } + }); + case "txt" -> { + if (messages.isEmpty()) { + System.out.println(Localization.lang("No integrity problems found.")); + } else { + messages.forEach(message -> { + if (message.field() != null) { + System.out.println(String.format("- %s: %s", message.field().getName(), message.message())); + } else { + System.out.println(String.format("- %s", message.message())); + } + }); + System.out.println(); + System.out.println(Localization.lang("Total integrity problems found: %0.", String.valueOf(messages.size()))); + } + } + case "csv" -> { + System.out.println("file,line,field,message"); + messages.forEach(message -> { + String line = message.getLineNumber().map(Object::toString).orElse(""); + String field = message.field() != null ? message.field().getName() : ""; + System.out.println(String.format("%s,%s,%s,%s", + inputFile, + line, + field, + message.message().replace("\"", "\"\""))); + }); + } + default -> { + System.out.println(Localization.lang("Unknown output format '%0'.", outputFormat)); + return 3; + } + } + } + + private void outputErrorFormat(List messages) { + messages.forEach(message -> { + if (message.field() != null) { + System.out.println(String.format("%s:%d: %s: %s", + inputFile, + message.getLineNumber().orElse(0), + message.field().getName(), + message.message())); + } else { + System.out.println(String.format("%s:%d: %s", + inputFile, + message.getLineNumber().orElse(0), + message.message())); + } + }); } } From 82723a65155e2cea5844d757bd70c116cf72a63e Mon Sep 17 00:00:00 2001 From: Philip <37398281+palukku@users.noreply.github.com> Date: Tue, 9 Sep 2025 23:50:00 +0200 Subject: [PATCH 02/25] add check integrity to jabkit --- .../org/jabref/cli/ArgumentProcessor.java | 2 +- .../java/org/jabref/cli/CheckIntegrity.java | 119 +++++++++--------- .../importer/fileformat/BibtexParser.java | 24 +++- .../java/org/jabref/model/entry/BibEntry.java | 54 ++++++++ 4 files changed, 134 insertions(+), 65 deletions(-) diff --git a/jabkit/src/main/java/org/jabref/cli/ArgumentProcessor.java b/jabkit/src/main/java/org/jabref/cli/ArgumentProcessor.java index 35e77c12e7d..e48b5e4c197 100644 --- a/jabkit/src/main/java/org/jabref/cli/ArgumentProcessor.java +++ b/jabkit/src/main/java/org/jabref/cli/ArgumentProcessor.java @@ -41,7 +41,7 @@ // sorted alphabetically subcommands = { CheckConsistency.class, -// CheckIntegrity.class, + CheckIntegrity.class, Convert.class, Fetch.class, GenerateBibFromAux.class, diff --git a/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java b/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java index 3ef47772d0f..b422689b096 100644 --- a/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java +++ b/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java @@ -1,6 +1,10 @@ package org.jabref.cli; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; import java.util.List; +import java.util.Locale; import java.util.Optional; import java.util.concurrent.Callable; @@ -70,72 +74,61 @@ public Integer call() { ); List messages = databaseContext.getEntries().stream() - .flatMap(entry -> integrityCheck.checkEntry(entry).stream()) - .toList(); - - switch (outputFormat) { - case "errorformat" -> messages.forEach(message -> { - if (message.field() != null) { - System.out.println(String.format("%s:%d: %s: %s", - inputFile, - message.getLineNumber().orElse(0), - message.field().getName(), - message.message())); - } else { - System.out.println(String.format("%s:%d: %s", - inputFile, - message.getLineNumber().orElse(0), - message.message())); + .flatMap(entry -> integrityCheck.checkEntry(entry).stream()) + .toList(); + try { + return switch (outputFormat.toLowerCase(Locale.ROOT)) { + case "errorformat" -> + outputErrorFormat(messages); + case "txt" -> + outputTxt(messages); + case "csv" -> + outputCsv(messages); + default -> { + System.out.println(Localization.lang("Unknown output format '%0'.", outputFormat)); + yield 3; } - }); - case "txt" -> { - if (messages.isEmpty()) { - System.out.println(Localization.lang("No integrity problems found.")); - } else { - messages.forEach(message -> { - if (message.field() != null) { - System.out.println(String.format("- %s: %s", message.field().getName(), message.message())); - } else { - System.out.println(String.format("- %s", message.message())); - } - }); - System.out.println(); - System.out.println(Localization.lang("Total integrity problems found: %0.", String.valueOf(messages.size()))); - } - } - case "csv" -> { - System.out.println("file,line,field,message"); - messages.forEach(message -> { - String line = message.getLineNumber().map(Object::toString).orElse(""); - String field = message.field() != null ? message.field().getName() : ""; - System.out.println(String.format("%s,%s,%s,%s", - inputFile, - line, - field, - message.message().replace("\"", "\"\""))); - }); - } - default -> { - System.out.println(Localization.lang("Unknown output format '%0'.", outputFormat)); - return 3; - } + }; + } catch ( + IOException e) { + System.err.println("Error writing to output: " + e.getMessage()); + } + return 1; + } + + private int outputCsv(List messages) throws IOException { + Writer writer = new OutputStreamWriter(System.out); + writer.write("Citation Key,Field,Message\n"); + for (IntegrityMessage message : messages) { + String citationKey = message.entry().getCitationKey().orElse(""); + String field = message.field() != null ? message.field().getDisplayName() : ""; + String msg = message.message().replace("\"", "\"\""); + writer.write("%s,%s,%s\n".formatted(citationKey, field, msg)); } + writer.flush(); + return 0; } - private void outputErrorFormat(List messages) { - messages.forEach(message -> { - if (message.field() != null) { - System.out.println(String.format("%s:%d: %s: %s", - inputFile, - message.getLineNumber().orElse(0), - message.field().getName(), - message.message())); - } else { - System.out.println(String.format("%s:%d: %s", - inputFile, - message.getLineNumber().orElse(0), - message.message())); - } - }); + private int outputTxt(List messages) throws IOException { + Writer writer = new OutputStreamWriter(System.out); + for (IntegrityMessage message : messages) { + writer.append(message.toString()).write("\n"); + } + writer.flush(); + return 0; + } + + private int outputErrorFormat(List messages) throws IOException { + Writer writer = new OutputStreamWriter(System.out); + for (IntegrityMessage message : messages) { + BibEntry.FieldRange fieldRange = message.entry().getFieldRangeFromField(message.field()); + writer.write("%s:%d:%d: %s\n".formatted( + inputFile, + fieldRange.startLine(), + fieldRange.startColumn(), + message.message())); + } + writer.flush(); + return 0; } } diff --git a/jablib/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java b/jablib/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java index 15bb0f2be02..d1649df36e7 100644 --- a/jablib/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java +++ b/jablib/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java @@ -19,6 +19,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.Stack; import java.util.function.Predicate; import java.util.regex.Pattern; @@ -90,7 +91,7 @@ */ public class BibtexParser implements Parser { private static final Logger LOGGER = LoggerFactory.getLogger(BibtexParser.class); - private static final Integer LOOKAHEAD = 1024; + private static final int LOOKAHEAD = 1024; private static final String BIB_DESK_ROOT_GROUP_NAME = "BibDeskGroups"; private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance(); private static final int INDEX_RELATIVE_PATH_IN_PLIST = 4; @@ -100,7 +101,13 @@ public class BibtexParser implements Parser { private BibDatabase database; private Set entryTypes; private boolean eof; + private int line = 1; + private int column = 1; + // Stores the last read column of the highest column number encountered on any line so far. + // In basic JDK data structures, there is no size-limited stack. We did not want to include Apache Commons Collections only for "CircularFifoBuffer" + private Stack highestColumns = new Stack<>(); + private ParserResult parserResult; private final MetaDataParser metaDataParser; private final Map parsedBibdeskGroups; @@ -634,6 +641,10 @@ private int read() throws IOException { } if (character == '\n') { line++; + highestColumns.push(column); + column = 1; + } else { + column++; } return character; } @@ -641,6 +652,9 @@ private int read() throws IOException { private void unread(int character) throws IOException { if (character == '\n') { line--; + column = highestColumns.pop(); + } else { + column--; } pushbackReader.unread(character); if (pureTextFromFile.getLast() == character) { @@ -679,6 +693,9 @@ private String parsePreamble() throws IOException { private BibEntry parseEntry(String entryType) throws IOException { BibEntry result = new BibEntry(EntryTypeFactory.parse(entryType)); + result.setStartLine(line); + result.setStartColumn(column); + skipWhitespace(); consume('{', '('); int character = peek(); @@ -713,10 +730,13 @@ private BibEntry parseEntry(String entryType) throws IOException { // Consume new line which signals end of entry skipOneNewline(); + result.setEndLine(line); + result.setEndColumn(column); return result; } private void parseField(BibEntry entry) throws IOException { + int startLine = line; Field field = FieldFactory.parseField(parseTextToken().toLowerCase(Locale.ROOT)); skipWhitespace(); @@ -770,6 +790,8 @@ private void parseField(BibEntry entry) throws IOException { } } } + + entry.getFieldRanges().put(field, new BibEntry.FieldRange(0, 0, line - startLine, column)); } private String parseFieldContent(Field field) throws IOException { diff --git a/jablib/src/main/java/org/jabref/model/entry/BibEntry.java b/jablib/src/main/java/org/jabref/model/entry/BibEntry.java index 55443db80ac..8b04ec45c09 100644 --- a/jablib/src/main/java/org/jabref/model/entry/BibEntry.java +++ b/jablib/src/main/java/org/jabref/model/entry/BibEntry.java @@ -130,6 +130,14 @@ public class BibEntry { */ private boolean changed; + private int startLine = -1; + private int endLine = -1; + + private int startColumn = -1; + private int endColumn = -1; + + private final Map fieldRanges = new HashMap<>(); + /** * Constructs a new BibEntry. The internal ID is set to IdGenerator.next() */ @@ -1286,4 +1294,50 @@ public boolean isEmpty() { } return StandardField.AUTOMATIC_FIELDS.containsAll(this.getFields()); } + + public int getStartLine() { + return startLine; + } + + public void setStartLine(int startLine) { + this.startLine = startLine; + } + + public int getEndLine() { + return endLine; + } + + public void setEndLine(int endLine) { + this.endLine = endLine; + } + + public int getStartColumn() { + return startColumn; + } + + public void setStartColumn(int startColumn) { + this.startColumn = startColumn; + } + + public int getEndColumn() { + return endColumn; + } + + public void setEndColumn(int endColumn) { + this.endColumn = endColumn; + } + + public Map getFieldRanges() { + return fieldRanges; + } + + public FieldRange getFieldRangeFromField(Field field) { + return fieldRanges.getOrDefault(field, fieldRanges.getOrDefault(field.getAlias().orElse(InternalField.KEY_FIELD), FieldRange.getNullRange())); + } + + public record FieldRange(int startLine, int startColumn, int endLine, int endColumn) { + public static FieldRange getNullRange() { + return new FieldRange(0, 0, 0, 0); + } + } } From da299eb42e6e7df45ec38047165ba546e766f2c2 Mon Sep 17 00:00:00 2001 From: Philip <37398281+palukku@users.noreply.github.com> Date: Wed, 10 Sep 2025 00:11:05 +0200 Subject: [PATCH 03/25] replace writer --- .../java/org/jabref/cli/CheckIntegrity.java | 30 ++++++++----------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java b/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java index b422689b096..90dce22a412 100644 --- a/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java +++ b/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java @@ -1,8 +1,6 @@ package org.jabref.cli; import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.Writer; import java.util.List; import java.util.Locale; import java.util.Optional; @@ -60,7 +58,6 @@ public Integer call() { if (!sharedOptions.porcelain) { System.out.println(Localization.lang("Checking integrity of '%0'.", inputFile)); - System.out.flush(); } BibDatabaseContext databaseContext = parserResult.get().getDatabaseContext(); @@ -74,8 +71,14 @@ public Integer call() { ); List messages = databaseContext.getEntries().stream() - .flatMap(entry -> integrityCheck.checkEntry(entry).stream()) + .flatMap(entry -> { + if (!sharedOptions.porcelain) { + System.out.println(Localization.lang("Checking entry with citation key '%0'.", entry.getCitationKey().orElse(""))); + } + return integrityCheck.checkEntry(entry).stream(); + }) .toList(); + try { return switch (outputFormat.toLowerCase(Locale.ROOT)) { case "errorformat" -> @@ -89,46 +92,37 @@ public Integer call() { yield 3; } }; - } catch ( - IOException e) { + } catch (IOException e) { System.err.println("Error writing to output: " + e.getMessage()); } return 1; } private int outputCsv(List messages) throws IOException { - Writer writer = new OutputStreamWriter(System.out); - writer.write("Citation Key,Field,Message\n"); + System.out.println("Citation Key,Field,Message"); for (IntegrityMessage message : messages) { String citationKey = message.entry().getCitationKey().orElse(""); String field = message.field() != null ? message.field().getDisplayName() : ""; String msg = message.message().replace("\"", "\"\""); - writer.write("%s,%s,%s\n".formatted(citationKey, field, msg)); + System.out.printf("%s,%s,%s%n", citationKey, field, msg); } - writer.flush(); return 0; } private int outputTxt(List messages) throws IOException { - Writer writer = new OutputStreamWriter(System.out); - for (IntegrityMessage message : messages) { - writer.append(message.toString()).write("\n"); - } - writer.flush(); + messages.forEach(System.out::println); return 0; } private int outputErrorFormat(List messages) throws IOException { - Writer writer = new OutputStreamWriter(System.out); for (IntegrityMessage message : messages) { BibEntry.FieldRange fieldRange = message.entry().getFieldRangeFromField(message.field()); - writer.write("%s:%d:%d: %s\n".formatted( + System.out.printf("%s:%d:%d: %s\n".formatted( inputFile, fieldRange.startLine(), fieldRange.startColumn(), message.message())); } - writer.flush(); return 0; } } From efebec98a50cdb212b37be88a0398ff62fb8819b Mon Sep 17 00:00:00 2001 From: Philip <37398281+palukku@users.noreply.github.com> Date: Wed, 10 Sep 2025 00:20:10 +0200 Subject: [PATCH 04/25] fix imports --- .../main/java/org/jabref/cli/CheckIntegrity.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java b/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java index 8ab0f7e71c4..9b663f3f25a 100644 --- a/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java +++ b/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java @@ -1,5 +1,15 @@ package org.jabref.cli; +import java.io.IOException; +import java.util.List; +import java.util.Locale; +import java.util.Optional; +import java.util.concurrent.Callable; + +import org.jabref.logic.importer.ParserResult; +import org.jabref.logic.integrity.IntegrityCheck; +import org.jabref.logic.integrity.IntegrityMessage; +import org.jabref.logic.journals.JournalAbbreviationLoader; import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; @@ -82,7 +92,8 @@ public Integer call() { yield 3; } }; - } catch (IOException e) { + } catch ( + IOException e) { System.err.println("Error writing to output: " + e.getMessage()); } return 1; From c49f597658fec6a526836aad423fae653990ceaa Mon Sep 17 00:00:00 2001 From: Philip <37398281+palukku@users.noreply.github.com> Date: Wed, 10 Sep 2025 00:23:07 +0200 Subject: [PATCH 05/25] fix format --- jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java b/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java index 9b663f3f25a..90dce22a412 100644 --- a/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java +++ b/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java @@ -92,8 +92,7 @@ public Integer call() { yield 3; } }; - } catch ( - IOException e) { + } catch (IOException e) { System.err.println("Error writing to output: " + e.getMessage()); } return 1; From 805cc426174d39463dac012e6762edb3981189f3 Mon Sep 17 00:00:00 2001 From: Philip <37398281+palukku@users.noreply.github.com> Date: Wed, 10 Sep 2025 00:44:01 +0200 Subject: [PATCH 06/25] add separator character handling --- jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java b/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java index 90dce22a412..0cf6a938318 100644 --- a/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java +++ b/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java @@ -103,7 +103,10 @@ private int outputCsv(List messages) throws IOException { for (IntegrityMessage message : messages) { String citationKey = message.entry().getCitationKey().orElse(""); String field = message.field() != null ? message.field().getDisplayName() : ""; - String msg = message.message().replace("\"", "\"\""); + String msg = message.message().replace("\"", "\\\""); + if (msg.contains(",")) { + msg = "\"" + msg + "\""; + } System.out.printf("%s,%s,%s%n", citationKey, field, msg); } return 0; From 2d8c404655b395dafb082fde7b1ae0bb08b2f4de Mon Sep 17 00:00:00 2001 From: Philip <37398281+palukku@users.noreply.github.com> Date: Wed, 10 Sep 2025 00:50:11 +0200 Subject: [PATCH 07/25] remove exception --- .../java/org/jabref/cli/CheckIntegrity.java | 38 ++++++++----------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java b/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java index 0cf6a938318..4a0290b8110 100644 --- a/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java +++ b/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java @@ -1,6 +1,5 @@ package org.jabref.cli; -import java.io.IOException; import java.util.List; import java.util.Locale; import java.util.Optional; @@ -73,32 +72,27 @@ public Integer call() { List messages = databaseContext.getEntries().stream() .flatMap(entry -> { if (!sharedOptions.porcelain) { - System.out.println(Localization.lang("Checking entry with citation key '%0'.", entry.getCitationKey().orElse(""))); + System.out.println(Localization.lang("Checking entry with citation key '%0'.", entry.getCitationKey().orElse(""))); } return integrityCheck.checkEntry(entry).stream(); }) .toList(); - try { - return switch (outputFormat.toLowerCase(Locale.ROOT)) { - case "errorformat" -> - outputErrorFormat(messages); - case "txt" -> - outputTxt(messages); - case "csv" -> - outputCsv(messages); - default -> { - System.out.println(Localization.lang("Unknown output format '%0'.", outputFormat)); - yield 3; - } - }; - } catch (IOException e) { - System.err.println("Error writing to output: " + e.getMessage()); - } - return 1; + return switch (outputFormat.toLowerCase(Locale.ROOT)) { + case "errorformat" -> + outputErrorFormat(messages); + case "txt" -> + outputTxt(messages); + case "csv" -> + outputCsv(messages); + default -> { + System.out.println(Localization.lang("Unknown output format '%0'.", outputFormat)); + yield 3; + } + }; } - private int outputCsv(List messages) throws IOException { + private int outputCsv(List messages) { System.out.println("Citation Key,Field,Message"); for (IntegrityMessage message : messages) { String citationKey = message.entry().getCitationKey().orElse(""); @@ -112,12 +106,12 @@ private int outputCsv(List messages) throws IOException { return 0; } - private int outputTxt(List messages) throws IOException { + private int outputTxt(List messages) { messages.forEach(System.out::println); return 0; } - private int outputErrorFormat(List messages) throws IOException { + private int outputErrorFormat(List messages) { for (IntegrityMessage message : messages) { BibEntry.FieldRange fieldRange = message.entry().getFieldRangeFromField(message.field()); System.out.printf("%s:%d:%d: %s\n".formatted( From 2e5b4e2ddbbc23a05c8eba52fc1a4ba0c2d35ad4 Mon Sep 17 00:00:00 2001 From: Philip <37398281+palukku@users.noreply.github.com> Date: Wed, 10 Sep 2025 01:04:40 +0200 Subject: [PATCH 08/25] adapt languages --- jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java | 2 +- jablib/src/main/resources/l10n/JabRef_en.properties | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java b/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java index 4a0290b8110..0982089e9d4 100644 --- a/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java +++ b/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java @@ -72,7 +72,7 @@ public Integer call() { List messages = databaseContext.getEntries().stream() .flatMap(entry -> { if (!sharedOptions.porcelain) { - System.out.println(Localization.lang("Checking entry with citation key '%0'.", entry.getCitationKey().orElse(""))); + System.out.println(Localization.lang("Checking integrity of '%0'", entry.getCitationKey().orElse(""))); } return integrityCheck.checkEntry(entry).stream(); }) diff --git a/jablib/src/main/resources/l10n/JabRef_en.properties b/jablib/src/main/resources/l10n/JabRef_en.properties index 73ea6b1055f..e6a54f67d92 100644 --- a/jablib/src/main/resources/l10n/JabRef_en.properties +++ b/jablib/src/main/resources/l10n/JabRef_en.properties @@ -387,6 +387,7 @@ Unknown\ export\ format\ %0=Unknown export format %0 Importing\ %0=Importing %0 Importing\ file\ %0\ as\ unknown\ format=Importing file %0 as unknown format Format\ used\:\ %0=Format used: %0 +Unknown\ output\ format\ '%0'.=Unknown output format '%0' Extension=Extension From fd0d992e55a9c52374a8b4a28c268ec4c03da338 Mon Sep 17 00:00:00 2001 From: Philip <37398281+palukku@users.noreply.github.com> Date: Wed, 10 Sep 2025 21:37:55 +0200 Subject: [PATCH 09/25] fix language tests --- jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java b/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java index 0982089e9d4..90c3bebb27d 100644 --- a/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java +++ b/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java @@ -72,7 +72,7 @@ public Integer call() { List messages = databaseContext.getEntries().stream() .flatMap(entry -> { if (!sharedOptions.porcelain) { - System.out.println(Localization.lang("Checking integrity of '%0'", entry.getCitationKey().orElse(""))); + System.out.println(Localization.lang("Checking integrity of '%0'.", entry.getCitationKey().orElse(""))); } return integrityCheck.checkEntry(entry).stream(); }) From 228838c7ee6e4f1d23e8d51ef1274abbf47d8684 Mon Sep 17 00:00:00 2001 From: Philip <37398281+palukku@users.noreply.github.com> Date: Wed, 10 Sep 2025 21:54:36 +0200 Subject: [PATCH 10/25] fix language tests 2 --- jablib/src/main/resources/l10n/JabRef_en.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jablib/src/main/resources/l10n/JabRef_en.properties b/jablib/src/main/resources/l10n/JabRef_en.properties index e6a54f67d92..0c50f677e0b 100644 --- a/jablib/src/main/resources/l10n/JabRef_en.properties +++ b/jablib/src/main/resources/l10n/JabRef_en.properties @@ -387,7 +387,7 @@ Unknown\ export\ format\ %0=Unknown export format %0 Importing\ %0=Importing %0 Importing\ file\ %0\ as\ unknown\ format=Importing file %0 as unknown format Format\ used\:\ %0=Format used: %0 -Unknown\ output\ format\ '%0'.=Unknown output format '%0' +Unknown\ output\ format\ '%0'.=Unknown output format '%0'. Extension=Extension From 743765d7711af0ea71816864a1db0f46c8f0dbc5 Mon Sep 17 00:00:00 2001 From: Philip <37398281+palukku@users.noreply.github.com> Date: Wed, 10 Sep 2025 22:53:19 +0200 Subject: [PATCH 11/25] change field ranges in BibtexParser --- .../org/jabref/logic/importer/fileformat/BibtexParser.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/jablib/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java b/jablib/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java index d99bbab7697..918c875f6f6 100644 --- a/jablib/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java +++ b/jablib/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java @@ -47,6 +47,7 @@ import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.FieldFactory; import org.jabref.model.entry.field.FieldProperty; +import org.jabref.model.entry.field.InternalField; import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.types.EntryTypeFactory; import org.jabref.model.groups.ExplicitGroup; @@ -702,7 +703,10 @@ private BibEntry parseEntry(String entryType) throws IOException { if ((character != '\n') && (character != '\r')) { skipWhitespace(); } + int keyStartLine = line; + int keyStartColumn = column; String key = parseKey(); + result.getFieldRanges().put(InternalField.KEY_FIELD, new BibEntry.FieldRange(keyStartLine, keyStartColumn, line, column)); result.setCitationKey(key); skipWhitespace(); @@ -737,6 +741,7 @@ private BibEntry parseEntry(String entryType) throws IOException { private void parseField(BibEntry entry) throws IOException { int startLine = line; + int startColumn = column; Field field = FieldFactory.parseField(parseTextToken().toLowerCase(Locale.ROOT)); skipWhitespace(); @@ -791,7 +796,7 @@ private void parseField(BibEntry entry) throws IOException { } } - entry.getFieldRanges().put(field, new BibEntry.FieldRange(0, 0, line - startLine, column)); + entry.getFieldRanges().put(field, new BibEntry.FieldRange(startLine, startColumn, line, column)); } private String parseFieldContent(Field field) throws IOException { From 244217c582919ffe1355de8199fd9d79fc0b953b Mon Sep 17 00:00:00 2001 From: palukku <37398281+palukku@users.noreply.github.com> Date: Thu, 11 Sep 2025 13:51:04 +0200 Subject: [PATCH 12/25] re-add CygWinPathConverter --- jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java b/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java index 90c3bebb27d..d80566f5a92 100644 --- a/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java +++ b/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java @@ -1,10 +1,12 @@ package org.jabref.cli; +import java.nio.file.Path; import java.util.List; import java.util.Locale; import java.util.Optional; import java.util.concurrent.Callable; +import org.jabref.cli.converter.CygWinPathConverter; import org.jabref.logic.importer.ParserResult; import org.jabref.logic.integrity.IntegrityCheck; import org.jabref.logic.integrity.IntegrityMessage; @@ -29,8 +31,8 @@ class CheckIntegrity implements Callable { @Mixin private ArgumentProcessor.SharedOptions sharedOptions = new ArgumentProcessor.SharedOptions(); - @Parameters(description = "BibTeX file to check", arity = "1") - private String inputFile; + @Parameters(converter = CygWinPathConverter.class, description = "BibTeX file to check", arity = "1") + private Path inputFile; @Option(names = {"--output-format"}, description = "Output format: errorformat, txt or csv", defaultValue = "errorformat") private String outputFormat; From 8fc9fd619c5588b359d3871dbf032b40528097e2 Mon Sep 17 00:00:00 2001 From: palukku <37398281+palukku@users.noreply.github.com> Date: Thu, 11 Sep 2025 16:05:56 +0200 Subject: [PATCH 13/25] fix modernizer --- .../java/org/jabref/logic/importer/fileformat/BibtexParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jablib/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java b/jablib/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java index 918c875f6f6..c375d6f3ef9 100644 --- a/jablib/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java +++ b/jablib/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java @@ -107,7 +107,7 @@ public class BibtexParser implements Parser { private int column = 1; // Stores the last read column of the highest column number encountered on any line so far. // In basic JDK data structures, there is no size-limited stack. We did not want to include Apache Commons Collections only for "CircularFifoBuffer" - private Stack highestColumns = new Stack<>(); + private Deque highestColumns = ; private ParserResult parserResult; private final MetaDataParser metaDataParser; From 483b3f07e68646ba2f1bb39d1469bb7b0ff4343c Mon Sep 17 00:00:00 2001 From: palukku <37398281+palukku@users.noreply.github.com> Date: Thu, 11 Sep 2025 17:00:25 +0200 Subject: [PATCH 14/25] fix modernizer2 --- jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java | 1 + .../org/jabref/logic/importer/fileformat/BibtexParser.java | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java b/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java index d80566f5a92..fcbad86601e 100644 --- a/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java +++ b/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java @@ -59,6 +59,7 @@ public Integer call() { if (!sharedOptions.porcelain) { System.out.println(Localization.lang("Checking integrity of '%0'.", inputFile)); + System.out.flush(); } BibDatabaseContext databaseContext = parserResult.get().getDatabaseContext(); diff --git a/jablib/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java b/jablib/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java index c375d6f3ef9..c6fe55cfafb 100644 --- a/jablib/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java +++ b/jablib/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java @@ -7,6 +7,7 @@ import java.io.Reader; import java.io.StringWriter; import java.nio.file.Path; +import java.util.ArrayDeque; import java.util.Base64; import java.util.Collection; import java.util.Deque; @@ -19,7 +20,6 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.Stack; import java.util.function.Predicate; import java.util.regex.Pattern; @@ -107,7 +107,7 @@ public class BibtexParser implements Parser { private int column = 1; // Stores the last read column of the highest column number encountered on any line so far. // In basic JDK data structures, there is no size-limited stack. We did not want to include Apache Commons Collections only for "CircularFifoBuffer" - private Deque highestColumns = ; + private final Deque highestColumns = new ArrayDeque<>(); private ParserResult parserResult; private final MetaDataParser metaDataParser; From c0bf69bd62ffca372a9c7ba98c5bda5b5ad07d4a Mon Sep 17 00:00:00 2001 From: palukku <37398281+palukku@users.noreply.github.com> Date: Thu, 11 Sep 2025 18:34:51 +0200 Subject: [PATCH 15/25] move fieldranges to parser --- .../java/org/jabref/cli/CheckIntegrity.java | 13 +++-- .../jabref/logic/importer/ParserResult.java | 20 +++++++ .../importer/fileformat/BibtexParser.java | 16 +++--- .../java/org/jabref/model/entry/BibEntry.java | 54 ------------------- 4 files changed, 38 insertions(+), 65 deletions(-) diff --git a/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java b/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java index fcbad86601e..31670391cd3 100644 --- a/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java +++ b/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java @@ -1,8 +1,10 @@ package org.jabref.cli; import java.nio.file.Path; +import java.util.HashMap; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Optional; import java.util.concurrent.Callable; @@ -13,7 +15,8 @@ import org.jabref.logic.journals.JournalAbbreviationLoader; import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabaseContext; -import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.Field; +import org.jabref.model.entry.field.InternalField; import picocli.CommandLine; @@ -83,7 +86,7 @@ public Integer call() { return switch (outputFormat.toLowerCase(Locale.ROOT)) { case "errorformat" -> - outputErrorFormat(messages); + outputErrorFormat(messages, parserResult.get()); case "txt" -> outputTxt(messages); case "csv" -> @@ -114,9 +117,11 @@ private int outputTxt(List messages) { return 0; } - private int outputErrorFormat(List messages) { + private int outputErrorFormat(List messages, ParserResult parserResult) { for (IntegrityMessage message : messages) { - BibEntry.FieldRange fieldRange = message.entry().getFieldRangeFromField(message.field()); + Map fieldRangeMap = parserResult.getFieldRanges().getOrDefault(message.entry(), new HashMap<>()); + ParserResult.Range fieldRange = fieldRangeMap.getOrDefault(message.field(), fieldRangeMap.getOrDefault(InternalField.KEY_FIELD, parserResult.getArticleRanges().getOrDefault(message.entry(), ParserResult.Range.getNullRange()))); + System.out.printf("%s:%d:%d: %s\n".formatted( inputFile, fieldRange.startLine(), diff --git a/jablib/src/main/java/org/jabref/logic/importer/ParserResult.java b/jablib/src/main/java/org/jabref/logic/importer/ParserResult.java index c0a75bc4903..aa64c7a0299 100644 --- a/jablib/src/main/java/org/jabref/logic/importer/ParserResult.java +++ b/jablib/src/main/java/org/jabref/logic/importer/ParserResult.java @@ -3,8 +3,10 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -14,6 +16,7 @@ import org.jabref.model.database.BibDatabases; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.BibEntryType; +import org.jabref.model.entry.field.Field; import org.jabref.model.metadata.MetaData; public class ParserResult { @@ -25,6 +28,9 @@ public class ParserResult { private boolean invalid; private boolean changedOnMigration = false; + private final Map articleRanges = new HashMap<>(); + private final Map> fieldRanges = new HashMap<>(); + public ParserResult() { this(List.of()); } @@ -147,4 +153,18 @@ public boolean getChangedOnMigration() { public void setChangedOnMigration(boolean wasChangedOnMigration) { this.changedOnMigration = wasChangedOnMigration; } + + public Map> getFieldRanges() { + return fieldRanges; + } + + public Map getArticleRanges() { + return articleRanges; + } + + public record Range(int startLine, int startColumn, int endLine, int endColumn) { + public static Range getNullRange() { + return new Range(0, 0, 0, 0); + } + } } diff --git a/jablib/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java b/jablib/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java index c6fe55cfafb..d132ca64947 100644 --- a/jablib/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java +++ b/jablib/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java @@ -694,8 +694,8 @@ private String parsePreamble() throws IOException { private BibEntry parseEntry(String entryType) throws IOException { BibEntry result = new BibEntry(EntryTypeFactory.parse(entryType)); - result.setStartLine(line); - result.setStartColumn(column); + int articleStartLine = line; + int articleStartColumn = column; skipWhitespace(); consume('{', '('); @@ -706,7 +706,10 @@ private BibEntry parseEntry(String entryType) throws IOException { int keyStartLine = line; int keyStartColumn = column; String key = parseKey(); - result.getFieldRanges().put(InternalField.KEY_FIELD, new BibEntry.FieldRange(keyStartLine, keyStartColumn, line, column)); + + ParserResult.Range keyRange = new ParserResult.Range(keyStartLine, keyStartColumn, line, column); + parserResult.getFieldRanges().computeIfAbsent(result, _ -> new HashMap<>()).put(InternalField.KEY_FIELD, keyRange); + result.setCitationKey(key); skipWhitespace(); @@ -734,8 +737,7 @@ private BibEntry parseEntry(String entryType) throws IOException { // Consume new line which signals end of entry skipOneNewline(); - result.setEndLine(line); - result.setEndColumn(column); + parserResult.getArticleRanges().put(result, new ParserResult.Range(articleStartLine, articleStartColumn, line, column)); return result; } @@ -795,8 +797,8 @@ private void parseField(BibEntry entry) throws IOException { } } } - - entry.getFieldRanges().put(field, new BibEntry.FieldRange(startLine, startColumn, line, column)); + ParserResult.Range keyRange = new ParserResult.Range(startLine, startColumn, line, column); + parserResult.getFieldRanges().computeIfAbsent(entry, _ -> new HashMap<>()).put(field, keyRange); } private String parseFieldContent(Field field) throws IOException { diff --git a/jablib/src/main/java/org/jabref/model/entry/BibEntry.java b/jablib/src/main/java/org/jabref/model/entry/BibEntry.java index 8b04ec45c09..55443db80ac 100644 --- a/jablib/src/main/java/org/jabref/model/entry/BibEntry.java +++ b/jablib/src/main/java/org/jabref/model/entry/BibEntry.java @@ -130,14 +130,6 @@ public class BibEntry { */ private boolean changed; - private int startLine = -1; - private int endLine = -1; - - private int startColumn = -1; - private int endColumn = -1; - - private final Map fieldRanges = new HashMap<>(); - /** * Constructs a new BibEntry. The internal ID is set to IdGenerator.next() */ @@ -1294,50 +1286,4 @@ public boolean isEmpty() { } return StandardField.AUTOMATIC_FIELDS.containsAll(this.getFields()); } - - public int getStartLine() { - return startLine; - } - - public void setStartLine(int startLine) { - this.startLine = startLine; - } - - public int getEndLine() { - return endLine; - } - - public void setEndLine(int endLine) { - this.endLine = endLine; - } - - public int getStartColumn() { - return startColumn; - } - - public void setStartColumn(int startColumn) { - this.startColumn = startColumn; - } - - public int getEndColumn() { - return endColumn; - } - - public void setEndColumn(int endColumn) { - this.endColumn = endColumn; - } - - public Map getFieldRanges() { - return fieldRanges; - } - - public FieldRange getFieldRangeFromField(Field field) { - return fieldRanges.getOrDefault(field, fieldRanges.getOrDefault(field.getAlias().orElse(InternalField.KEY_FIELD), FieldRange.getNullRange())); - } - - public record FieldRange(int startLine, int startColumn, int endLine, int endColumn) { - public static FieldRange getNullRange() { - return new FieldRange(0, 0, 0, 0); - } - } } From cf7924afc39dcb74931c64396d017bea65dff41f Mon Sep 17 00:00:00 2001 From: palukku <37398281+palukku@users.noreply.github.com> Date: Thu, 11 Sep 2025 20:57:29 +0200 Subject: [PATCH 16/25] added changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c266844ca4..598e6ffffba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv ### Added +- We added the integrity check to the jabkit cli application. [#13848](https://github.com/JabRef/jabref/issues/13848) - We added support for Cygwin-file paths on a Windows Operating System. [#13274](https://github.com/JabRef/jabref/issues/13274) - We fixed an issue where "Print preview" would throw a `NullPointerException` if no printers were available. [#13708](https://github.com/JabRef/jabref/issues/13708) - We added the option to enable the language server in the preferences. [#13697](https://github.com/JabRef/jabref/pull/13697) From 4b9ac218ab19f24347f85d35232d86cfb0229eca Mon Sep 17 00:00:00 2001 From: Philip Date: Thu, 11 Sep 2025 22:43:37 +0200 Subject: [PATCH 17/25] change HashMap to IdentityHashMap --- .../main/java/org/jabref/logic/importer/ParserResult.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/jablib/src/main/java/org/jabref/logic/importer/ParserResult.java b/jablib/src/main/java/org/jabref/logic/importer/ParserResult.java index aa64c7a0299..476b02f2ab7 100644 --- a/jablib/src/main/java/org/jabref/logic/importer/ParserResult.java +++ b/jablib/src/main/java/org/jabref/logic/importer/ParserResult.java @@ -5,6 +5,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.HashSet; +import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -28,8 +29,8 @@ public class ParserResult { private boolean invalid; private boolean changedOnMigration = false; - private final Map articleRanges = new HashMap<>(); - private final Map> fieldRanges = new HashMap<>(); + private final Map articleRanges = new IdentityHashMap<>(); + private final Map> fieldRanges = new IdentityHashMap<>(); public ParserResult() { this(List.of()); @@ -167,4 +168,5 @@ public static Range getNullRange() { return new Range(0, 0, 0, 0); } } + } From 764c5bac8aaefe21253fd91ba8486e29bce7ed1d Mon Sep 17 00:00:00 2001 From: Philip Date: Thu, 11 Sep 2025 22:44:12 +0200 Subject: [PATCH 18/25] remove line --- jablib/src/main/java/org/jabref/logic/importer/ParserResult.java | 1 - 1 file changed, 1 deletion(-) diff --git a/jablib/src/main/java/org/jabref/logic/importer/ParserResult.java b/jablib/src/main/java/org/jabref/logic/importer/ParserResult.java index 476b02f2ab7..f0311c4ba71 100644 --- a/jablib/src/main/java/org/jabref/logic/importer/ParserResult.java +++ b/jablib/src/main/java/org/jabref/logic/importer/ParserResult.java @@ -168,5 +168,4 @@ public static Range getNullRange() { return new Range(0, 0, 0, 0); } } - } From 58180f7b2206b10e4f9b7567c29706861094c97b Mon Sep 17 00:00:00 2001 From: Philip Date: Thu, 11 Sep 2025 22:45:19 +0200 Subject: [PATCH 19/25] remove unused import --- jablib/src/main/java/org/jabref/logic/importer/ParserResult.java | 1 - 1 file changed, 1 deletion(-) diff --git a/jablib/src/main/java/org/jabref/logic/importer/ParserResult.java b/jablib/src/main/java/org/jabref/logic/importer/ParserResult.java index f0311c4ba71..ba803b6e15e 100644 --- a/jablib/src/main/java/org/jabref/logic/importer/ParserResult.java +++ b/jablib/src/main/java/org/jabref/logic/importer/ParserResult.java @@ -3,7 +3,6 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.List; From 10231e0e80f7df64b22ad370f419e9a81a4a4a83 Mon Sep 17 00:00:00 2001 From: Philip Date: Thu, 11 Sep 2025 23:22:12 +0200 Subject: [PATCH 20/25] add IntegrityCheckResultWriter --- .../java/org/jabref/cli/CheckIntegrity.java | 72 +++++++------------ .../IntegrityCheckResultCsvWriter.java | 29 ++++++++ ...IntegrityCheckResultErrorFormatWriter.java | 42 +++++++++++ .../IntegrityCheckResultTxtWriter.java | 20 ++++++ .../integrity/IntegrityCheckResultWriter.java | 19 +++++ 5 files changed, 135 insertions(+), 47 deletions(-) create mode 100644 jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultCsvWriter.java create mode 100644 jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultErrorFormatWriter.java create mode 100644 jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultTxtWriter.java create mode 100644 jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultWriter.java diff --git a/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java b/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java index 31670391cd3..f02e24ce979 100644 --- a/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java +++ b/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java @@ -1,23 +1,28 @@ package org.jabref.cli; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; import java.nio.file.Path; -import java.util.HashMap; import java.util.List; import java.util.Locale; -import java.util.Map; import java.util.Optional; import java.util.concurrent.Callable; import org.jabref.cli.converter.CygWinPathConverter; import org.jabref.logic.importer.ParserResult; import org.jabref.logic.integrity.IntegrityCheck; +import org.jabref.logic.integrity.IntegrityCheckResultCsvWriter; +import org.jabref.logic.integrity.IntegrityCheckResultErrorFormatWriter; +import org.jabref.logic.integrity.IntegrityCheckResultTxtWriter; +import org.jabref.logic.integrity.IntegrityCheckResultWriter; import org.jabref.logic.integrity.IntegrityMessage; import org.jabref.logic.journals.JournalAbbreviationLoader; import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabaseContext; -import org.jabref.model.entry.field.Field; -import org.jabref.model.entry.field.InternalField; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import picocli.CommandLine; import static picocli.CommandLine.Command; @@ -28,6 +33,8 @@ @Command(name = "check-integrity", description = "Check integrity of the database.") class CheckIntegrity implements Callable { + private static final Logger LOGGER = LoggerFactory.getLogger(CheckIntegrity.class); + @CommandLine.ParentCommand private ArgumentProcessor argumentProcessor; @@ -75,58 +82,29 @@ public Integer call() { allowIntegerEdition ); - List messages = databaseContext.getEntries().stream() - .flatMap(entry -> { - if (!sharedOptions.porcelain) { - System.out.println(Localization.lang("Checking integrity of '%0'.", entry.getCitationKey().orElse(""))); - } - return integrityCheck.checkEntry(entry).stream(); - }) - .toList(); + List messages = integrityCheck.checkDatabase(databaseContext.getDatabase()); - return switch (outputFormat.toLowerCase(Locale.ROOT)) { + Writer writer = new OutputStreamWriter(System.out); + IntegrityCheckResultWriter checkResultWriter; + switch (outputFormat.toLowerCase(Locale.ROOT)) { case "errorformat" -> - outputErrorFormat(messages, parserResult.get()); + checkResultWriter = new IntegrityCheckResultErrorFormatWriter(writer, messages, parserResult.get(), inputFile); case "txt" -> - outputTxt(messages); + checkResultWriter = new IntegrityCheckResultTxtWriter(writer, messages); case "csv" -> - outputCsv(messages); + checkResultWriter = new IntegrityCheckResultCsvWriter(writer, messages); default -> { System.out.println(Localization.lang("Unknown output format '%0'.", outputFormat)); - yield 3; - } - }; - } - - private int outputCsv(List messages) { - System.out.println("Citation Key,Field,Message"); - for (IntegrityMessage message : messages) { - String citationKey = message.entry().getCitationKey().orElse(""); - String field = message.field() != null ? message.field().getDisplayName() : ""; - String msg = message.message().replace("\"", "\\\""); - if (msg.contains(",")) { - msg = "\"" + msg + "\""; + return 3; } - System.out.printf("%s,%s,%s%n", citationKey, field, msg); } - return 0; - } - private int outputTxt(List messages) { - messages.forEach(System.out::println); - return 0; - } - - private int outputErrorFormat(List messages, ParserResult parserResult) { - for (IntegrityMessage message : messages) { - Map fieldRangeMap = parserResult.getFieldRanges().getOrDefault(message.entry(), new HashMap<>()); - ParserResult.Range fieldRange = fieldRangeMap.getOrDefault(message.field(), fieldRangeMap.getOrDefault(InternalField.KEY_FIELD, parserResult.getArticleRanges().getOrDefault(message.entry(), ParserResult.Range.getNullRange()))); - - System.out.printf("%s:%d:%d: %s\n".formatted( - inputFile, - fieldRange.startLine(), - fieldRange.startColumn(), - message.message())); + try { + checkResultWriter.writeFindings(); + writer.flush(); + } catch (IOException e) { + LOGGER.error("Error writing results", e); + return 2; } return 0; } diff --git a/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultCsvWriter.java b/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultCsvWriter.java new file mode 100644 index 00000000000..c782474200d --- /dev/null +++ b/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultCsvWriter.java @@ -0,0 +1,29 @@ +package org.jabref.logic.integrity; + +import java.io.IOException; +import java.io.Writer; +import java.util.List; + +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVPrinter; + +public class IntegrityCheckResultCsvWriter extends IntegrityCheckResultWriter { + + private CSVPrinter csvPrinter; + + public IntegrityCheckResultCsvWriter(Writer writer, List messages) { + super(writer, messages); + } + + @Override + public void writeFindings() throws IOException { + csvPrinter = new CSVPrinter(writer, CSVFormat.DEFAULT); + csvPrinter.printRecord("Citation Key", "Field", "Message"); + csvPrinter.printRecords(messages.stream().map(message -> List.of(message.entry().getCitationKey().orElse(""), message.field().getDisplayName(), message.message()))); + } + + @Override + public void close() throws IOException { + csvPrinter.close(); + } +} diff --git a/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultErrorFormatWriter.java b/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultErrorFormatWriter.java new file mode 100644 index 00000000000..bcdad4162ef --- /dev/null +++ b/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultErrorFormatWriter.java @@ -0,0 +1,42 @@ +package org.jabref.logic.integrity; + +import java.io.IOException; +import java.io.Writer; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.jabref.logic.importer.ParserResult; +import org.jabref.model.entry.field.Field; +import org.jabref.model.entry.field.InternalField; + +public class IntegrityCheckResultErrorFormatWriter extends IntegrityCheckResultWriter { + + private final ParserResult parserResult; + private Path inputFile; + + public IntegrityCheckResultErrorFormatWriter(Writer writer, List messages, ParserResult parserResult, Path inputFile) { + super(writer, messages); + this.parserResult = parserResult; + this.inputFile = inputFile; + } + + @Override + public void writeFindings() throws IOException { + for (IntegrityMessage message : messages) { + Map fieldRangeMap = parserResult.getFieldRanges().getOrDefault(message.entry(), new HashMap<>()); + ParserResult.Range fieldRange = fieldRangeMap.getOrDefault(message.field(), fieldRangeMap.getOrDefault(InternalField.KEY_FIELD, parserResult.getArticleRanges().getOrDefault(message.entry(), ParserResult.Range.getNullRange()))); + + System.out.printf("%s:%d:%d: %s\n".formatted( + inputFile, + fieldRange.startLine(), + fieldRange.startColumn(), + message.message())); + } + } + + @Override + public void close() throws IOException { + } +} diff --git a/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultTxtWriter.java b/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultTxtWriter.java new file mode 100644 index 00000000000..32b26337ee4 --- /dev/null +++ b/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultTxtWriter.java @@ -0,0 +1,20 @@ +package org.jabref.logic.integrity; + +import java.io.IOException; +import java.io.Writer; +import java.util.List; + +public class IntegrityCheckResultTxtWriter extends IntegrityCheckResultWriter { + + public IntegrityCheckResultTxtWriter(Writer writer, List messages) { + super(writer, messages); + } + + @Override + public void writeFindings() throws IOException { + messages.forEach(System.out::println); + } + + @Override + public void close() throws IOException { } +} diff --git a/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultWriter.java b/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultWriter.java new file mode 100644 index 00000000000..1daee0f451c --- /dev/null +++ b/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultWriter.java @@ -0,0 +1,19 @@ +package org.jabref.logic.integrity; + +import java.io.Closeable; +import java.io.IOException; +import java.io.Writer; +import java.util.List; + +public abstract class IntegrityCheckResultWriter implements Closeable { + + protected final List messages; + protected final Writer writer; + + public IntegrityCheckResultWriter(Writer writer, List messages) { + this.writer = writer; + this.messages = messages; + } + + public abstract void writeFindings() throws IOException; +} From f92d8f61d8d3987c73a444189773123de13dff04 Mon Sep 17 00:00:00 2001 From: Philip Date: Thu, 11 Sep 2025 23:55:00 +0200 Subject: [PATCH 21/25] address comments --- docs/requirements/cli.md | 14 ++++++++++++++ .../main/java/org/jabref/cli/CheckConsistency.java | 1 + .../main/java/org/jabref/cli/CheckIntegrity.java | 5 +++-- jabkit/src/main/java/org/jabref/cli/Convert.java | 1 + .../java/org/jabref/cli/GenerateBibFromAux.java | 6 ++++-- .../java/org/jabref/cli/GenerateCitationKeys.java | 6 ++++-- jabkit/src/main/java/org/jabref/cli/PdfUpdate.java | 6 ++++-- jabkit/src/main/java/org/jabref/cli/Search.java | 1 + .../org/jabref/logic/importer/ParserResult.java | 4 +--- .../logic/importer/fileformat/BibtexParser.java | 1 + .../IntegrityCheckResultErrorFormatWriter.java | 6 +++--- .../integrity/IntegrityCheckResultTxtWriter.java | 8 ++++++-- 12 files changed, 43 insertions(+), 16 deletions(-) create mode 100644 docs/requirements/cli.md diff --git a/docs/requirements/cli.md b/docs/requirements/cli.md new file mode 100644 index 00000000000..7694adac9dd --- /dev/null +++ b/docs/requirements/cli.md @@ -0,0 +1,14 @@ +--- +parent: Requirements +--- +# CLI + +## Unified `--input` option across all commands +`req~jabkit.cli.input-flag~1` + +All `jabkit` commands that need a file input must have the `--input` option to specify the input file. +See [ADR 45](../decisions/0045-use-input-flag-always-for-input-files.md) for more details. + +Needs: impl + + \ No newline at end of file diff --git a/jabkit/src/main/java/org/jabref/cli/CheckConsistency.java b/jabkit/src/main/java/org/jabref/cli/CheckConsistency.java index 59e369f1c5c..9af8ecce73f 100644 --- a/jabkit/src/main/java/org/jabref/cli/CheckConsistency.java +++ b/jabkit/src/main/java/org/jabref/cli/CheckConsistency.java @@ -33,6 +33,7 @@ class CheckConsistency implements Callable { @Mixin private ArgumentProcessor.SharedOptions sharedOptions = new ArgumentProcessor.SharedOptions(); + // [impl->req~jabkit.cli.input-flag~1] @Option(names = {"--input"}, converter = CygWinPathConverter.class, description = "Input BibTeX file", required = true) private Path inputFile; diff --git a/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java b/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java index f02e24ce979..c13f11f2ce1 100644 --- a/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java +++ b/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java @@ -28,7 +28,6 @@ import static picocli.CommandLine.Command; import static picocli.CommandLine.Mixin; import static picocli.CommandLine.Option; -import static picocli.CommandLine.Parameters; @Command(name = "check-integrity", description = "Check integrity of the database.") class CheckIntegrity implements Callable { @@ -41,12 +40,14 @@ class CheckIntegrity implements Callable { @Mixin private ArgumentProcessor.SharedOptions sharedOptions = new ArgumentProcessor.SharedOptions(); - @Parameters(converter = CygWinPathConverter.class, description = "BibTeX file to check", arity = "1") + // [impl->req~jabkit.cli.input-flag~1] + @Option(names = {"--input"}, converter = CygWinPathConverter.class, description = "Input BibTeX file", required = true) private Path inputFile; @Option(names = {"--output-format"}, description = "Output format: errorformat, txt or csv", defaultValue = "errorformat") private String outputFormat; + // in BibTeX it could be preferences.getEntryEditorPreferences().shouldAllowIntegerEditionBibtex() @Option(names = {"--allow-integer-edition"}, description = "Allows Integer edition: true or false", defaultValue = "true") private boolean allowIntegerEdition = true; diff --git a/jabkit/src/main/java/org/jabref/cli/Convert.java b/jabkit/src/main/java/org/jabref/cli/Convert.java index f2f2700462b..59617349f28 100644 --- a/jabkit/src/main/java/org/jabref/cli/Convert.java +++ b/jabkit/src/main/java/org/jabref/cli/Convert.java @@ -37,6 +37,7 @@ public class Convert implements Runnable { @Mixin private ArgumentProcessor.SharedOptions sharedOptions = new ArgumentProcessor.SharedOptions(); + // [impl->req~jabkit.cli.input-flag~1] @Option(names = {"--input"}, converter = CygWinPathConverter.class, description = "Input file", required = true) private Path inputFile; diff --git a/jabkit/src/main/java/org/jabref/cli/GenerateBibFromAux.java b/jabkit/src/main/java/org/jabref/cli/GenerateBibFromAux.java index 99d6c461a1a..ef88e995fa7 100644 --- a/jabkit/src/main/java/org/jabref/cli/GenerateBibFromAux.java +++ b/jabkit/src/main/java/org/jabref/cli/GenerateBibFromAux.java @@ -5,6 +5,7 @@ import java.util.Optional; import java.util.stream.Collectors; +import org.jabref.cli.converter.CygWinPathConverter; import org.jabref.logic.auxparser.AuxParser; import org.jabref.logic.auxparser.AuxParserResult; import org.jabref.logic.auxparser.AuxParserStatisticsProvider; @@ -35,8 +36,9 @@ class GenerateBibFromAux implements Runnable { @Option(names = "--aux", required = true) private Path auxFile; - @Option(names = "--input", required = true) - private String inputFile; + // [impl->req~jabkit.cli.input-flag~1] + @Option(names = {"--input"}, converter = CygWinPathConverter.class, description = "Input BibTeX file", required = true) + private Path inputFile; @Option(names = "--output") private Path outputFile; diff --git a/jabkit/src/main/java/org/jabref/cli/GenerateCitationKeys.java b/jabkit/src/main/java/org/jabref/cli/GenerateCitationKeys.java index 7aa8614a297..db4d2a820d3 100644 --- a/jabkit/src/main/java/org/jabref/cli/GenerateCitationKeys.java +++ b/jabkit/src/main/java/org/jabref/cli/GenerateCitationKeys.java @@ -4,6 +4,7 @@ import java.util.Optional; import java.util.stream.Collectors; +import org.jabref.cli.converter.CygWinPathConverter; import org.jabref.logic.citationkeypattern.CitationKeyGenerator; import org.jabref.logic.importer.ParserResult; import org.jabref.logic.l10n.Localization; @@ -28,8 +29,9 @@ public class GenerateCitationKeys implements Runnable { @Mixin private ArgumentProcessor.SharedOptions sharedOptions = new ArgumentProcessor.SharedOptions(); - @Option(names = "--input", description = "The input .bib file.", required = true) - private String inputFile; + // [impl->req~jabkit.cli.input-flag~1] + @Option(names = {"--input"}, converter = CygWinPathConverter.class, description = "Input BibTeX file", required = true) + private Path inputFile; @Option(names = "--output", description = "The output .bib file.") private Path outputFile; diff --git a/jabkit/src/main/java/org/jabref/cli/PdfUpdate.java b/jabkit/src/main/java/org/jabref/cli/PdfUpdate.java index 60d15ab1a5a..08ebb73d135 100644 --- a/jabkit/src/main/java/org/jabref/cli/PdfUpdate.java +++ b/jabkit/src/main/java/org/jabref/cli/PdfUpdate.java @@ -9,6 +9,7 @@ import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; +import org.jabref.cli.converter.CygWinPathConverter; import org.jabref.logic.FilePreferences; import org.jabref.logic.bibtex.FieldPreferences; import org.jabref.logic.exporter.EmbeddedBibFilePdfExporter; @@ -49,8 +50,9 @@ class PdfUpdate implements Runnable { @Option(names = {"-k", "--citation-key"}, description = "Citation keys", required = true) private List citationKeys = List.of(); // ToDo: check dedault value - @Option(names = "--input", description = "Input file", required = true) - private Path inputFile; // Local files only + // [impl->req~jabkit.cli.input-flag~1] + @Option(names = {"--input"}, converter = CygWinPathConverter.class, description = "Input BibTeX file", required = true) + private Path inputFile; @Option(names = "--input-format", description = "Input format of the file", required = true) private String inputFormat = "*"; diff --git a/jabkit/src/main/java/org/jabref/cli/Search.java b/jabkit/src/main/java/org/jabref/cli/Search.java index 1b77e4f7592..a2fb4526f29 100644 --- a/jabkit/src/main/java/org/jabref/cli/Search.java +++ b/jabkit/src/main/java/org/jabref/cli/Search.java @@ -47,6 +47,7 @@ class Search implements Runnable { @Option(names = {"--query"}, description = "Search query", required = true) private String query; + // [impl->req~jabkit.cli.input-flag~1] @Option(names = {"--input"}, converter = CygWinPathConverter.class, description = "Input BibTeX file", required = true) private Path inputFile; diff --git a/jablib/src/main/java/org/jabref/logic/importer/ParserResult.java b/jablib/src/main/java/org/jabref/logic/importer/ParserResult.java index ba803b6e15e..da436eb4af7 100644 --- a/jablib/src/main/java/org/jabref/logic/importer/ParserResult.java +++ b/jablib/src/main/java/org/jabref/logic/importer/ParserResult.java @@ -163,8 +163,6 @@ public Map getArticleRanges() { } public record Range(int startLine, int startColumn, int endLine, int endColumn) { - public static Range getNullRange() { - return new Range(0, 0, 0, 0); - } + public static final Range NULL_RANGE = new Range(0, 0, 0, 0); } } diff --git a/jablib/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java b/jablib/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java index d132ca64947..ea6c1423f89 100644 --- a/jablib/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java +++ b/jablib/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java @@ -106,6 +106,7 @@ public class BibtexParser implements Parser { private int line = 1; private int column = 1; // Stores the last read column of the highest column number encountered on any line so far. + // The intended data structure is Stack, but it is not used because Java code style checkers complain. // In basic JDK data structures, there is no size-limited stack. We did not want to include Apache Commons Collections only for "CircularFifoBuffer" private final Deque highestColumns = new ArrayDeque<>(); diff --git a/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultErrorFormatWriter.java b/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultErrorFormatWriter.java index bcdad4162ef..4fc6909aa1b 100644 --- a/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultErrorFormatWriter.java +++ b/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultErrorFormatWriter.java @@ -14,7 +14,7 @@ public class IntegrityCheckResultErrorFormatWriter extends IntegrityCheckResultWriter { private final ParserResult parserResult; - private Path inputFile; + private final Path inputFile; public IntegrityCheckResultErrorFormatWriter(Writer writer, List messages, ParserResult parserResult, Path inputFile) { super(writer, messages); @@ -26,9 +26,9 @@ public IntegrityCheckResultErrorFormatWriter(Writer writer, List fieldRangeMap = parserResult.getFieldRanges().getOrDefault(message.entry(), new HashMap<>()); - ParserResult.Range fieldRange = fieldRangeMap.getOrDefault(message.field(), fieldRangeMap.getOrDefault(InternalField.KEY_FIELD, parserResult.getArticleRanges().getOrDefault(message.entry(), ParserResult.Range.getNullRange()))); + ParserResult.Range fieldRange = fieldRangeMap.getOrDefault(message.field(), fieldRangeMap.getOrDefault(InternalField.KEY_FIELD, parserResult.getArticleRanges().getOrDefault(message.entry(), ParserResult.Range.NULL_RANGE))); - System.out.printf("%s:%d:%d: %s\n".formatted( + writer.write("%s:%d:%d: %s\n".formatted( inputFile, fieldRange.startLine(), fieldRange.startColumn(), diff --git a/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultTxtWriter.java b/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultTxtWriter.java index 32b26337ee4..8a5871a88b9 100644 --- a/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultTxtWriter.java +++ b/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultTxtWriter.java @@ -12,9 +12,13 @@ public IntegrityCheckResultTxtWriter(Writer writer, List messa @Override public void writeFindings() throws IOException { - messages.forEach(System.out::println); + for (IntegrityMessage message : messages) { + writer.write(message.toString()); + writer.write(System.lineSeparator()); + } } @Override - public void close() throws IOException { } + public void close() throws IOException { + } } From 58aee67cb8dd3d17ec4682a21a423b0cd7612b01 Mon Sep 17 00:00:00 2001 From: Philip Date: Fri, 12 Sep 2025 00:05:09 +0200 Subject: [PATCH 22/25] add immutable Map#of --- .../logic/integrity/IntegrityCheckResultErrorFormatWriter.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultErrorFormatWriter.java b/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultErrorFormatWriter.java index 4fc6909aa1b..404ace10f52 100644 --- a/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultErrorFormatWriter.java +++ b/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultErrorFormatWriter.java @@ -3,7 +3,6 @@ import java.io.IOException; import java.io.Writer; import java.nio.file.Path; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -25,7 +24,7 @@ public IntegrityCheckResultErrorFormatWriter(Writer writer, List fieldRangeMap = parserResult.getFieldRanges().getOrDefault(message.entry(), new HashMap<>()); + Map fieldRangeMap = parserResult.getFieldRanges().getOrDefault(message.entry(), Map.of()); ParserResult.Range fieldRange = fieldRangeMap.getOrDefault(message.field(), fieldRangeMap.getOrDefault(InternalField.KEY_FIELD, parserResult.getArticleRanges().getOrDefault(message.entry(), ParserResult.Range.NULL_RANGE))); writer.write("%s:%d:%d: %s\n".formatted( From fef7e2ec9547964e29847c33fb5361d00d5111c6 Mon Sep 17 00:00:00 2001 From: Philip Date: Fri, 12 Sep 2025 10:18:19 +0200 Subject: [PATCH 23/25] updated option handling and fix check --- .../src/main/java/org/jabref/cli/CheckIntegrity.java | 12 +++++++++--- .../IntegrityCheckResultErrorFormatWriter.java | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java b/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java index c13f11f2ce1..8fa5a93da57 100644 --- a/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java +++ b/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java @@ -8,6 +8,7 @@ import java.util.Locale; import java.util.Optional; import java.util.concurrent.Callable; +import java.util.stream.Collectors; import org.jabref.cli.converter.CygWinPathConverter; import org.jabref.logic.importer.ParserResult; @@ -48,11 +49,12 @@ class CheckIntegrity implements Callable { private String outputFormat; // in BibTeX it could be preferences.getEntryEditorPreferences().shouldAllowIntegerEditionBibtex() - @Option(names = {"--allow-integer-edition"}, description = "Allows Integer edition: true or false", defaultValue = "true") - private boolean allowIntegerEdition = true; + @Option(names = {"--allow-integer-edition"}, description = "Allows Integer edition", negatable = true, defaultValue = "true", fallbackValue = "true") + private boolean allowIntegerEdition; @Override public Integer call() { + System.err.println(allowIntegerEdition); Optional parserResult = ArgumentProcessor.importFile( inputFile, "bibtex", @@ -83,7 +85,11 @@ public Integer call() { allowIntegerEdition ); - List messages = integrityCheck.checkDatabase(databaseContext.getDatabase()); + List messages = databaseContext.getEntries().stream() + .flatMap(entry -> integrityCheck.checkEntry(entry).stream()) + .collect(Collectors.toList()); + + messages.addAll(integrityCheck.checkDatabase(databaseContext.getDatabase())); Writer writer = new OutputStreamWriter(System.out); IntegrityCheckResultWriter checkResultWriter; diff --git a/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultErrorFormatWriter.java b/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultErrorFormatWriter.java index 404ace10f52..a41e15d7aab 100644 --- a/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultErrorFormatWriter.java +++ b/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultErrorFormatWriter.java @@ -27,7 +27,7 @@ public void writeFindings() throws IOException { Map fieldRangeMap = parserResult.getFieldRanges().getOrDefault(message.entry(), Map.of()); ParserResult.Range fieldRange = fieldRangeMap.getOrDefault(message.field(), fieldRangeMap.getOrDefault(InternalField.KEY_FIELD, parserResult.getArticleRanges().getOrDefault(message.entry(), ParserResult.Range.NULL_RANGE))); - writer.write("%s:%d:%d: %s\n".formatted( + writer.append("%s:%d:%d: %s\n".formatted( inputFile, fieldRange.startLine(), fieldRange.startColumn(), From eff8e17f7af16a89642839297fd8689b7eca54aa Mon Sep 17 00:00:00 2001 From: Philip Date: Fri, 12 Sep 2025 10:19:14 +0200 Subject: [PATCH 24/25] remove test output --- jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java | 1 - 1 file changed, 1 deletion(-) diff --git a/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java b/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java index 8fa5a93da57..b74ed7e42b0 100644 --- a/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java +++ b/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java @@ -54,7 +54,6 @@ class CheckIntegrity implements Callable { @Override public Integer call() { - System.err.println(allowIntegerEdition); Optional parserResult = ArgumentProcessor.importFile( inputFile, "bibtex", From 4bd013ed189ce7266213f65ad6046353a9735224 Mon Sep 17 00:00:00 2001 From: Philip Date: Fri, 12 Sep 2025 10:59:15 +0200 Subject: [PATCH 25/25] add comment on constructor handling the writer and close --- .../integrity/IntegrityCheckResultErrorFormatWriter.java | 4 ---- .../jabref/logic/integrity/IntegrityCheckResultTxtWriter.java | 4 ---- .../jabref/logic/integrity/IntegrityCheckResultWriter.java | 4 ++++ 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultErrorFormatWriter.java b/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultErrorFormatWriter.java index a41e15d7aab..15cb7ee53ae 100644 --- a/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultErrorFormatWriter.java +++ b/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultErrorFormatWriter.java @@ -34,8 +34,4 @@ public void writeFindings() throws IOException { message.message())); } } - - @Override - public void close() throws IOException { - } } diff --git a/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultTxtWriter.java b/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultTxtWriter.java index 8a5871a88b9..7bfb6e3c876 100644 --- a/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultTxtWriter.java +++ b/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultTxtWriter.java @@ -17,8 +17,4 @@ public void writeFindings() throws IOException { writer.write(System.lineSeparator()); } } - - @Override - public void close() throws IOException { - } } diff --git a/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultWriter.java b/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultWriter.java index 1daee0f451c..9edf3da71ff 100644 --- a/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultWriter.java +++ b/jablib/src/main/java/org/jabref/logic/integrity/IntegrityCheckResultWriter.java @@ -10,10 +10,14 @@ public abstract class IntegrityCheckResultWriter implements Closeable { protected final List messages; protected final Writer writer; + /// Writer lifecycle: The caller is responsible for closing the writer at the appropriate time. public IntegrityCheckResultWriter(Writer writer, List messages) { this.writer = writer; this.messages = messages; } public abstract void writeFindings() throws IOException; + + @Override + public void close() throws IOException { } }