Skip to content

Simplify BibDatabaseWriter by removing unnecessary subclass #13408

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
import org.jabref.gui.maintable.columns.MainTableColumn;
import org.jabref.logic.bibtex.InvalidFieldValueException;
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.SelfContainedSaveConfiguration;
import org.jabref.logic.preferences.CliPreferences;
import org.jabref.logic.util.BackupFileType;
Expand Down Expand Up @@ -281,7 +281,7 @@ void performBackup(Path backupPath) {
// This MUST NOT create a broken backup file that then jabref wants to "restore" from?
try (Writer writer = new AtomicFileWriter(backupPath, encoding, false)) {
BibWriter bibWriter = new BibWriter(writer, bibDatabaseContext.getDatabase().getNewLineSeparator());
new BibtexDatabaseWriter(
new BibDatabaseWriter(
bibWriter,
saveConfiguration,
preferences.getFieldPreferences(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
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.SaveException;
import org.jabref.logic.exporter.SelfContainedSaveConfiguration;
import org.jabref.logic.l10n.Encodings;
Expand Down Expand Up @@ -263,7 +262,7 @@ private boolean saveDatabase(Path file, boolean selectedOnly, Charset encoding,
synchronized (bibDatabaseContext) {
try (AtomicFileWriter fileWriter = new AtomicFileWriter(file, encoding, saveConfiguration.shouldMakeBackup())) {
BibWriter bibWriter = new BibWriter(fileWriter, bibDatabaseContext.getDatabase().getNewLineSeparator());
BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter(
BibDatabaseWriter databaseWriter = new BibDatabaseWriter(
bibWriter,
saveConfiguration,
preferences.getFieldPreferences(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
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.SelfContainedSaveConfiguration;
import org.jabref.logic.preferences.CliPreferences;
import org.jabref.model.database.BibDatabase;
Expand Down Expand Up @@ -66,7 +65,7 @@ void setup(@TempDir Path tempDir) throws IOException {
private void saveDatabase() throws IOException {
try (Writer writer = new AtomicFileWriter(testBib, StandardCharsets.UTF_8, false)) {
BibWriter bibWriter = new BibWriter(writer, bibDatabaseContext.getDatabase().getNewLineSeparator());
new BibtexDatabaseWriter(
new BibDatabaseWriter(
bibWriter,
saveConfiguration,
preferences.getFieldPreferences(),
Expand Down
3 changes: 1 addition & 2 deletions jabkit/src/main/java/org/jabref/cli/ArgumentProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
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.ExporterFactory;
import org.jabref.logic.exporter.SelfContainedSaveConfiguration;
import org.jabref.logic.importer.FetcherException;
Expand Down Expand Up @@ -156,7 +155,7 @@ protected static void saveDatabase(CliPreferences cliPreferences,
BibWriter bibWriter = new BibWriter(fileWriter, OS.NEWLINE);
SelfContainedSaveConfiguration saveConfiguration = (SelfContainedSaveConfiguration) new SelfContainedSaveConfiguration()
.withReformatOnSave(cliPreferences.getLibraryPreferences().shouldAlwaysReformatOnSave());
BibDatabaseWriter databaseWriter = new BibtexDatabaseWriter(
BibDatabaseWriter databaseWriter = new BibDatabaseWriter(
bibWriter,
saveConfiguration,
cliPreferences.getFieldPreferences(),
Expand Down
3 changes: 1 addition & 2 deletions jablib/src/jmh/java/org/jabref/benchmarks/Benchmarks.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import org.jabref.logic.citationkeypattern.CitationKeyPatternPreferences;
import org.jabref.logic.exporter.BibDatabaseWriter;
import org.jabref.logic.exporter.BibWriter;
import org.jabref.logic.exporter.BibtexDatabaseWriter;
import org.jabref.logic.exporter.SelfContainedSaveConfiguration;
import org.jabref.logic.formatter.bibtexfields.HtmlToLatexFormatter;
import org.jabref.logic.importer.ParserResult;
Expand Down Expand Up @@ -83,7 +82,7 @@ private StringWriter getOutputWriter() throws IOException {
FieldPreferences fieldPreferences = new FieldPreferences(true, List.of(), List.of());
CitationKeyPatternPreferences citationKeyPatternPreferences = mock(CitationKeyPatternPreferences.class, Answers.RETURNS_DEEP_STUBS);

BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter(
BibDatabaseWriter databaseWriter = new BibDatabaseWriter(
bibWriter,
saveConfiguration,
fieldPreferences,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
import org.jabref.logic.citationkeypattern.CitationKeyGenerator;
import org.jabref.logic.database.DatabaseMerger;
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.SaveException;
import org.jabref.logic.exporter.SelfContainedSaveConfiguration;
import org.jabref.logic.git.SlrGitHandler;
Expand Down Expand Up @@ -431,7 +431,7 @@ private void writeResultToFile(Path pathToFile, BibDatabaseContext context) thro
.withSaveOrder(context.getMetaData().getSaveOrder().map(SelfContainedSaveOrder::of).orElse(SaveOrder.getDefaultSaveOrder()))
.withReformatOnSave(preferences.getLibraryPreferences().shouldAlwaysReformatOnSave());
BibWriter bibWriter = new BibWriter(fileWriter, OS.NEWLINE);
BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter(
BibDatabaseWriter databaseWriter = new BibDatabaseWriter(
bibWriter,
saveConfiguration,
preferences.getFieldPreferences(),
Expand Down
127 changes: 111 additions & 16 deletions jablib/src/main/java/org/jabref/logic/exporter/BibDatabaseWriter.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.jabref.logic.exporter;

import java.io.IOException;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
Expand All @@ -16,7 +17,10 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.jabref.logic.bibtex.BibEntryWriter;
import org.jabref.logic.bibtex.FieldPreferences;
import org.jabref.logic.bibtex.FieldWriter;
import org.jabref.logic.bibtex.InvalidFieldValueException;
import org.jabref.logic.bibtex.comparator.BibtexStringComparator;
import org.jabref.logic.bibtex.comparator.CrossRefEntryComparator;
import org.jabref.logic.bibtex.comparator.FieldComparator;
Expand Down Expand Up @@ -44,20 +48,25 @@
import org.jabref.model.strings.StringUtil;

import org.jooq.lambda.Unchecked;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* A generic writer for our database. This is independent of the concrete serialization format.
* For instance, we could also write out YAML or XML by subclassing this class.
* Writes a .bib file following the BibTeX / BibLaTeX format using the provided {@link BibWriter}
* <p>
* Currently, {@link BibtexDatabaseWriter} is the only subclass of this class (and that class writes a .bib file)
* <p>
* The opposite class is {@link org.jabref.logic.importer.fileformat.BibtexParser}
* The opposite class is {@link org.jabref.logic.importer.fileformat.BibtexImporter}
*/
public abstract class BibDatabaseWriter {

public class BibDatabaseWriter {
public enum SaveType { WITH_JABREF_META_DATA, PLAIN_BIBTEX }

public static final String DATABASE_ID_PREFIX = "DBID:";
private static final Logger LOGGER = LoggerFactory.getLogger(BibDatabaseWriter.class);
private static final Pattern REFERENCE_PATTERN = Pattern.compile("(#[A-Za-z]+#)"); // Used to detect string references in strings
private static final String COMMENT_PREFIX = "@Comment";
private static final String PREAMBLE_PREFIX = "@Preamble";

private static final String STRING_PREFIX = "@String";

protected final BibWriter bibWriter;
protected final SelfContainedSaveConfiguration saveConfiguration;
protected final CitationKeyPatternPreferences keyPatternPreferences;
Expand All @@ -78,6 +87,19 @@ public BibDatabaseWriter(BibWriter bibWriter,
assert saveConfiguration.getSaveOrder().getOrderType() != SaveOrder.OrderType.TABLE;
}

public BibDatabaseWriter(Writer writer,
String newline,
SelfContainedSaveConfiguration saveConfiguration,
FieldPreferences fieldPreferences,
CitationKeyPatternPreferences citationKeyPatternPreferences,
BibEntryTypesManager entryTypesManager) {
this(new BibWriter(writer, newline),
saveConfiguration,
fieldPreferences,
citationKeyPatternPreferences,
entryTypesManager);
}

private static List<FieldChange> applySaveActions(List<BibEntry> toChange, MetaData metaData, FieldPreferences fieldPreferences) {
List<FieldChange> changes = new ArrayList<>();

Expand Down Expand Up @@ -224,11 +246,31 @@ public void savePartOfDatabase(BibDatabaseContext bibDatabaseContext, List<BibEn
writeEpilogue(bibDatabaseContext.getDatabase().getEpilog());
}

protected abstract void writeProlog(BibDatabaseContext bibDatabaseContext, Charset encoding) throws IOException;
protected void writeProlog(BibDatabaseContext bibDatabaseContext, Charset encoding) throws IOException {
// We write the encoding if
// - it is provided (!= null)
// - explicitly set in the .bib file OR not equal to UTF_8
// Otherwise, we do not write anything and return
if ((encoding == null) || (!bibDatabaseContext.getMetaData().getEncodingExplicitlySupplied() && (encoding.equals(StandardCharsets.UTF_8)))) {
return;
}

// Writes the file encoding information.
bibWriter.write("% ");
Comment on lines +258 to +259
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment is redundant and simply restates what the code does. It doesn't provide any additional information or reasoning about why this is necessary.

bibWriter.writeLine(SaveConfiguration.ENCODING_PREFIX + encoding);
}

protected abstract void writeEntry(BibEntry entry, BibDatabaseMode mode) throws IOException;
protected void writeEntry(BibEntry entry, BibDatabaseMode mode) throws IOException {
BibEntryWriter bibtexEntryWriter = new BibEntryWriter(new FieldWriter(fieldPreferences), entryTypesManager);
bibtexEntryWriter.write(entry, bibWriter, mode, saveConfiguration.shouldReformatFile());
}

protected abstract void writeEpilogue(String epilogue) throws IOException;
protected void writeEpilogue(String epilogue) throws IOException {
if (!StringUtil.isNullOrEmpty(epilogue)) {
bibWriter.write(epilogue);
bibWriter.finishBlock();
}
}

/**
* Writes all data to the specified writer, using each object's toString() method.
Expand All @@ -244,11 +286,31 @@ protected void writeMetaData(MetaData metaData, GlobalCitationKeyPatterns global
}
}

protected abstract void writeMetaDataItem(Map.Entry<String, String> metaItem) throws IOException;
protected void writeMetaDataItem(Map.Entry<String, String> metaItem) throws IOException {
bibWriter.write(COMMENT_PREFIX + "{");
bibWriter.write(MetaData.META_FLAG);
bibWriter.write(metaItem.getKey());
bibWriter.write(":");
bibWriter.write(metaItem.getValue());
bibWriter.write("}");
bibWriter.finishBlock();
}

protected abstract void writePreamble(String preamble) throws IOException;
protected void writePreamble(String preamble) throws IOException {
if (!StringUtil.isNullOrEmpty(preamble)) {
bibWriter.write(PREAMBLE_PREFIX + "{");
bibWriter.write(preamble);
bibWriter.writeLine("}");
bibWriter.finishBlock();
}
}

protected abstract void writeDatabaseID(String sharedDatabaseID) throws IOException;
protected void writeDatabaseID(String sharedDatabaseID) throws IOException {
bibWriter.write("% ");
bibWriter.write(DATABASE_ID_PREFIX);
bibWriter.write(" ");
bibWriter.writeLine(sharedDatabaseID);
}

/**
* Write all strings in alphabetical order, modified to produce a safe (for BibTeX) order of the strings if they
Expand Down Expand Up @@ -307,16 +369,49 @@ protected void writeString(BibtexString bibtexString, Map<String, BibtexString>
writeString(bibtexString, maxKeyLength);
}

protected abstract void writeString(BibtexString bibtexString, int maxKeyLength)
throws IOException;
protected void writeString(BibtexString bibtexString, int maxKeyLength) throws IOException {
// If the string has not been modified, write it back as it was
if (!saveConfiguration.shouldReformatFile() && !bibtexString.hasChanged()) {
LOGGER.debug("Writing parsed serialization {}.", bibtexString.getParsedSerialization());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug logging statement provides no additional value beyond what's obvious from the code. This violates the principle that comments/logs should add new information.

bibWriter.write(bibtexString.getParsedSerialization());
return;
}

// Write user comments
String userComments = bibtexString.getUserComments();
Comment on lines +380 to +381
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment merely restates what the code does without providing additional context or reasoning. This type of trivial comment should be removed.

if (!userComments.isEmpty()) {
bibWriter.writeLine(userComments);
}

bibWriter.write(STRING_PREFIX + "{" + bibtexString.getName() + StringUtil
.repeatSpaces(maxKeyLength - bibtexString.getName().length()) + " = ");
if (bibtexString.getContent().isEmpty()) {
bibWriter.write("{}");
} else {
try {
String formatted = new FieldWriter(fieldPreferences)
.write(InternalField.BIBTEX_STRING, bibtexString.getContent());
bibWriter.write(formatted);
} catch (InvalidFieldValueException ex) {
throw new IOException(ex);
}
}

bibWriter.writeLine("}");
}

protected void writeEntryTypeDefinitions(SortedSet<BibEntryType> types) throws IOException {
for (BibEntryType type : types) {
writeEntryTypeDefinition(type);
}
}

protected abstract void writeEntryTypeDefinition(BibEntryType customType) throws IOException;
protected void writeEntryTypeDefinition(BibEntryType customType) throws IOException {
bibWriter.write(COMMENT_PREFIX + "{");
bibWriter.write(MetaDataSerializer.serializeCustomEntryTypes(customType));
bibWriter.writeLine("}");
bibWriter.finishBlock();
}

/**
* Generate keys for all entries that are lacking keys.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import org.jabref.model.strings.StringUtil;

/**
* Class to write to a .bib file. Used by {@link BibtexDatabaseWriter}
* Class to write to a .bib file. Used by {@link BibDatabaseWriter}
*/
public class BibWriter {

Expand Down
Loading
Loading