diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f4987d0a02..b2f8e42e06c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve - We added an option to search the available citation styles by name in the preferences [#8108](https://github.com/JabRef/jabref/issues/8108) - We added an option to generate bib-entries from ID through a popover in the toolbar. [#4183](https://github.com/JabRef/jabref/issues/4183) - We added a menu option in the right click menu of the main table tabs to display the library properties. [#6527](https://github.com/JabRef/jabref/issues/6527) +- When a `.bib` file ("library") was saved successfully, a notification is shown ### Changed @@ -66,6 +67,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve - We fixed an issue where importing PDFs resulted in an uncaught exception [#8143](https://github.com/JabRef/jabref/issues/8143) - We fixed "The library has been modified by another program" showing up when line breaks change [#4877](https://github.com/JabRef/jabref/issues/4877) - The default directory of the "LaTeX Citations" tab is now the directory of the currently opened database (and not the directory chosen at the last open file dialog or the last database save) [koppor#538](https://github.com/koppor/jabref/issues/538) +- When writing a bib file, the `NegativeArraySizeException` should not occur [#8231](https://github.com/JabRef/jabref/issues/8231) [#8265](https://github.com/JabRef/jabref/issues/8265) - We fixed an issue where right-clicking on a tab and selecting close will close the focused tab even if it is not the tab we right-clicked [#8193](https://github.com/JabRef/jabref/pull/8193) - We fixed an issue where selecting a citation style in the preferences would sometimes produce an exception [#7860](https://github.com/JabRef/jabref/issues/7860) - We fixed an issue where an exception would occur when clicking on a DOI link in the preview pane [#7706](https://github.com/JabRef/jabref/issues/7706) diff --git a/src/main/java/org/jabref/gui/DialogService.java b/src/main/java/org/jabref/gui/DialogService.java index 367b81bda0d..26cf46c00a1 100644 --- a/src/main/java/org/jabref/gui/DialogService.java +++ b/src/main/java/org/jabref/gui/DialogService.java @@ -215,7 +215,7 @@ Optional showCustomButtonDialogAndWait(Alert.AlertType type, String Optional showBackgroundProgressDialogAndWait(String title, String content, StateManager stateManager); /** - * Notify the user in an non-blocking way (i.e., in form of toast in a snackbar). + * Notify the user in a non-blocking way (i.e., in form of toast in a snackbar). * * @param message the message to show. */ diff --git a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java index 5ae882116e9..c6bdf64e531 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java @@ -214,6 +214,7 @@ private boolean save(Path targetPath, SaveDatabaseMode mode) { libraryTab.getUndoManager().markUnchanged(); libraryTab.resetChangedProperties(); } + dialogService.notify(Localization.lang("Library saved")); return success; } catch (SaveException ex) { LOGGER.error(String.format("A problem occurred when trying to save the file %s", targetPath), ex); diff --git a/src/main/java/org/jabref/logic/bibtex/BibEntryWriter.java b/src/main/java/org/jabref/logic/bibtex/BibEntryWriter.java index ef93f2878cb..9794b32058f 100644 --- a/src/main/java/org/jabref/logic/bibtex/BibEntryWriter.java +++ b/src/main/java/org/jabref/logic/bibtex/BibEntryWriter.java @@ -93,7 +93,7 @@ private void writeRequiredFieldsFirstRemainingFieldsSecond(BibEntry entry, BibWr Set written = new HashSet<>(); written.add(InternalField.KEY_FIELD); - int indentation = getLengthOfLongestFieldName(entry); + final int indent = getLengthOfLongestFieldName(entry); Optional type = entryTypesManager.enrich(entry.getType(), bibDatabaseMode); if (type.isPresent()) { @@ -106,7 +106,7 @@ private void writeRequiredFieldsFirstRemainingFieldsSecond(BibEntry entry, BibWr .collect(Collectors.toList()); for (Field field : requiredFields) { - writeField(entry, out, field, indentation); + writeField(entry, out, field, indent); } // Then optional fields @@ -118,7 +118,7 @@ private void writeRequiredFieldsFirstRemainingFieldsSecond(BibEntry entry, BibWr .collect(Collectors.toList()); for (Field field : optionalFields) { - writeField(entry, out, field, indentation); + writeField(entry, out, field, indent); } written.addAll(requiredFields); @@ -131,7 +131,7 @@ private void writeRequiredFieldsFirstRemainingFieldsSecond(BibEntry entry, BibWr .collect(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Field::getName)))); for (Field field : remainingFields) { - writeField(entry, out, field, indentation); + writeField(entry, out, field, indent); } // Finally, end the entry. @@ -151,12 +151,13 @@ private void writeKeyField(BibEntry entry, BibWriter out) throws IOException { * @param field the field * @throws IOException In case of an IO error */ - private void writeField(BibEntry entry, BibWriter out, Field field, int indentation) throws IOException { + private void writeField(BibEntry entry, BibWriter out, Field field, int indent) throws IOException { Optional value = entry.getField(field); // only write field if it is not empty // field.ifPresent does not work as an IOException may be thrown if (value.isPresent() && !value.get().trim().isEmpty()) { - out.write(" " + getFormattedFieldName(field, indentation)); + out.write(" "); + out.write(getFormattedFieldName(field, indent)); try { out.write(fieldWriter.write(field, value.get())); } catch (InvalidFieldValueException ex) { @@ -166,7 +167,7 @@ private void writeField(BibEntry entry, BibWriter out, Field field, int indentat } } - private int getLengthOfLongestFieldName(BibEntry entry) { + static int getLengthOfLongestFieldName(BibEntry entry) { Predicate isNotCitationKey = field -> !InternalField.KEY_FIELD.equals(field); return entry.getFields() .stream() @@ -177,7 +178,7 @@ private int getLengthOfLongestFieldName(BibEntry entry) { } /** - * Get display version of a entry field. + * Get display version of an entry field. *

* BibTeX is case-insensitive therefore there is no difference between: howpublished, HOWPUBLISHED, HowPublished, etc. *

@@ -188,8 +189,8 @@ private int getLengthOfLongestFieldName(BibEntry entry) { * @param field The name of the field. * @return The display version of the field name. */ - private String getFormattedFieldName(Field field, int intention) { + static String getFormattedFieldName(Field field, int indent) { String fieldName = field.getName(); - return fieldName.toLowerCase(Locale.ROOT) + StringUtil.repeatSpaces(intention - fieldName.length()) + " = "; + return fieldName.toLowerCase(Locale.ROOT) + StringUtil.repeatSpaces(indent - fieldName.length()) + " = "; } } diff --git a/src/main/java/org/jabref/model/strings/StringUtil.java b/src/main/java/org/jabref/model/strings/StringUtil.java index ab4266733c7..47c4158e5b9 100644 --- a/src/main/java/org/jabref/model/strings/StringUtil.java +++ b/src/main/java/org/jabref/model/strings/StringUtil.java @@ -138,7 +138,7 @@ public static String join(String[] strings, String separator, int from, int to) return ""; } - int updatedFrom = Math.max(from, 0); + int updatedFrom = Math.max(0, from); int updatedTo = Math.min(strings.length, to); StringBuilder stringBuilder = new StringBuilder(); @@ -600,7 +600,7 @@ public static String replaceSpecialCharacters(String s) { * @return String with n spaces */ public static String repeatSpaces(int n) { - return repeat(n, ' '); + return repeat(Math.max(0, n), ' '); } /** diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 82d18865ed9..13bfc059c62 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -753,13 +753,11 @@ Save\ before\ closing=Save before closing Save\ library=Save library Save\ library\ as...=Save library as... - -Saved\ selected\ to\ '%0'.=Saved selected to '%0'. - Saving=Saving Saving\ all\ libraries...=Saving all libraries... - Saving\ library=Saving library +Library\ saved=Library saved +Saved\ selected\ to\ '%0'.=Saved selected to '%0'. Search=Search diff --git a/src/test/java/org/jabref/logic/bibtex/BibEntryWriterTest.java b/src/test/java/org/jabref/logic/bibtex/BibEntryWriterTest.java index a1b1c0370c8..497b2564a34 100644 --- a/src/test/java/org/jabref/logic/bibtex/BibEntryWriterTest.java +++ b/src/test/java/org/jabref/logic/bibtex/BibEntryWriterTest.java @@ -7,6 +7,7 @@ import java.util.Collection; import java.util.List; import java.util.Set; +import java.util.stream.Stream; import org.jabref.logic.exporter.BibWriter; import org.jabref.logic.importer.ImportFormatPreferences; @@ -18,7 +19,9 @@ import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.entry.LinkedFile; import org.jabref.model.entry.field.Field; +import org.jabref.model.entry.field.FieldFactory; import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.types.EntryTypeFactory; import org.jabref.model.entry.types.StandardEntryType; import org.jabref.model.entry.types.UnknownEntryType; import org.jabref.model.util.DummyFileUpdateMonitor; @@ -26,6 +29,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.mockito.Answers; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -464,7 +470,7 @@ void monthFieldSpecialSyntax() throws IOException { Collection entries = result.getDatabase().getEntries(); BibEntry entry = entries.iterator().next(); - // modify month field + // check month field Set fields = entry.getFields(); assertTrue(fields.contains(StandardField.MONTH)); assertEquals("#mar#", entry.getField(StandardField.MONTH).get()); @@ -475,6 +481,51 @@ void monthFieldSpecialSyntax() throws IOException { assertEquals(bibtexEntry, stringWriter.toString()); } + @Test + void customTypeCanBewritten() throws IOException { + // @formatter:off + String bibtexEntry = "@reference{Broecker1984,\n" + + "title = {International Center of Photography},\n" + + "subtitle = {Encyclopedia of Photography},\n" + + "editor = {Broecker, William L.},\n" + + "date = {1984},\n" + + "eprint = {305515791},\n" + + "eprinttype = {scribd},\n" + + "isbn = {0-517-55271-X},\n" + + "keywords = {g:photography, p:positive, c:silver, m:albumen, c:pigment, m:carbon, g:reference, c:encyclopedia},\n" + + "location = {New York},\n" + + "pagetotal = {678},\n" + + "publisher = {Crown},\n" + + "}\n"; + // @formatter:on + + // read in bibtex string + ParserResult result = new BibtexParser(importFormatPreferences, fileMonitor).parse(new StringReader(bibtexEntry)); + Collection entries = result.getDatabase().getEntries(); + BibEntry entry = entries.iterator().next(); + + entry.setField(FieldFactory.parseField("location"), "NY"); + + // write out bibtex string + bibEntryWriter.write(entry, bibWriter, BibDatabaseMode.BIBTEX); + + String expected = "@Reference{Broecker1984," + OS.NEWLINE + + " date = {1984}," + OS.NEWLINE + + " editor = {Broecker, William L.}," + OS.NEWLINE + + " eprint = {305515791}," + OS.NEWLINE + + " eprinttype = {scribd}," + OS.NEWLINE + + " isbn = {0-517-55271-X}," + OS.NEWLINE + + " keywords = {g:photography, p:positive, c:silver, m:albumen, c:pigment, m:carbon, g:reference, c:encyclopedia}," + OS.NEWLINE + + " location = {NY}," + OS.NEWLINE + + " pagetotal = {678}," + OS.NEWLINE + + " publisher = {Crown}," + OS.NEWLINE + + " subtitle = {Encyclopedia of Photography}," + OS.NEWLINE + + " title = {International Center of Photography}," + OS.NEWLINE + + "}" + OS.NEWLINE; + + assertEquals(expected, stringWriter.toString()); + } + @Test void constantMonthApril() throws Exception { BibEntry entry = new BibEntry(StandardEntryType.Misc) @@ -713,6 +764,38 @@ void testSerializeAll() throws IOException { // @formatter:on assertEquals(expected1 + OS.NEWLINE + expected2, output); + } + + static Stream testGetFormattedFieldNameData() { + return Stream.of( + Arguments.of(" = ", "", 0), + Arguments.of("a = ", "a", 0), + Arguments.of(" = ", "", 2), + Arguments.of("a = ", "a", 2), + Arguments.of("abc = ", "abc", 2), + Arguments.of("abcdef = ", "abcdef", 6) + ); + } + + @ParameterizedTest + @MethodSource("testGetFormattedFieldNameData") + void testGetFormattedFieldName(String expected, String fieldName, int indent) { + Field field = FieldFactory.parseField(fieldName); + assertEquals(expected, bibEntryWriter.getFormattedFieldName(field, indent)); + } + + static Stream testGetLengthOfLongestFieldNameData() { + return Stream.of( + Arguments.of(1, new BibEntry().withField(FieldFactory.parseField("t"), "t")), + Arguments.of(5, new BibEntry(EntryTypeFactory.parse("reference")) + .withCitationKey("Broecker1984") + .withField(StandardField.TITLE, "International Center of Photography}")) + ); + } + @ParameterizedTest + @MethodSource("testGetLengthOfLongestFieldNameData") + void testGetLengthOfLongestFieldName(int expected, BibEntry entry) { + assertEquals(expected, bibEntryWriter.getLengthOfLongestFieldName(entry)); } } diff --git a/src/test/java/org/jabref/model/strings/StringUtilTest.java b/src/test/java/org/jabref/model/strings/StringUtilTest.java index 393f94ad283..d68a832dc3a 100644 --- a/src/test/java/org/jabref/model/strings/StringUtilTest.java +++ b/src/test/java/org/jabref/model/strings/StringUtilTest.java @@ -308,11 +308,19 @@ void replaceSpecialCharactersWithNonNormalizedUnicode() { assertEquals("Modele", StringUtil.replaceSpecialCharacters("Modè€le")); } - @Test - void testRepeatSpaces() { - assertEquals("", StringUtil.repeatSpaces(0)); - assertEquals(" ", StringUtil.repeatSpaces(1)); - assertEquals(" ", StringUtil.repeatSpaces(7)); + static Stream testRepeatSpacesData() { + return Stream.of( + Arguments.of("", -1), + Arguments.of("", 0), + Arguments.of(" ", 1), + Arguments.of(" ", 7) + ); + } + + @ParameterizedTest + @MethodSource("testRepeatSpacesData") + void testRepeatSpaces(String result, int count) { + assertEquals(result, StringUtil.repeatSpaces(count)); } @Test