diff --git a/CHANGELOG.md b/CHANGELOG.md index 247520fab32..48ec7d25c32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -94,6 +94,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve - We fixed an issue where the password for a shared SQL database was not remembered [#6869](https://github.com/JabRef/jabref/issues/6869) - We fixed an issue where newly added entires were not synced to a shared SQL database [#7176](https://github.com/JabRef/jabref/issues/7176) - We fixed an issue where the PDF-Content importer threw an exception when no DOI number is present at the first page of the PDF document [#7203](https://github.com/JabRef/jabref/issues/7203) +- We fixed an issue where groups created from aux files did not update on file changes [#6394](https://github.com/JabRef/jabref/issues/6394) - We fixed an issue where authors that only have last names were incorrectly identified as institutes when generating citation keys [#7199](https://github.com/JabRef/jabref/issues/7199) - We fixed an issue where institutes were incorrectly identified as universities when generating citation keys [#6942](https://github.com/JabRef/jabref/issues/6942) diff --git a/src/main/java/org/jabref/gui/collab/GroupChangeViewModel.java b/src/main/java/org/jabref/gui/collab/GroupChangeViewModel.java index d907a04db08..92a08c39884 100644 --- a/src/main/java/org/jabref/gui/collab/GroupChangeViewModel.java +++ b/src/main/java/org/jabref/gui/collab/GroupChangeViewModel.java @@ -24,21 +24,22 @@ public GroupChangeViewModel(GroupDiff diff) { @Override public void makeChange(BibDatabaseContext database, NamedCompound undoEdit) { - GroupTreeNode root = database.getMetaData().getGroups().orElse(null); - if (root == null) { - root = new GroupTreeNode(DefaultGroupsFactory.getAllEntriesGroup()); - database.getMetaData().setGroups(root); - } + GroupTreeNode root = database.getMetaData().getGroups().orElseGet(() -> { + GroupTreeNode groupTreeNode = new GroupTreeNode(DefaultGroupsFactory.getAllEntriesGroup()); + database.getMetaData().setGroups(groupTreeNode); + return groupTreeNode; + }); + final UndoableModifySubtree undo = new UndoableModifySubtree( new GroupTreeNodeViewModel(database.getMetaData().getGroups().orElse(null)), new GroupTreeNodeViewModel(root), Localization.lang("Modified groups")); root.removeAllChildren(); if (changedGroups == null) { // I think setting root to null is not possible - root.setGroup(DefaultGroupsFactory.getAllEntriesGroup()); + root.setGroup(DefaultGroupsFactory.getAllEntriesGroup(), false, false, null); } else { // change root group, even though it'll be AllEntries anyway - root.setGroup(changedGroups.getGroup()); + root.setGroup(changedGroups.getGroup(), false, false, null); for (GroupTreeNode child : changedGroups.getChildren()) { child.copySubtree().moveTo(root); } diff --git a/src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java b/src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java index e3effaa582e..a3c8044ccf4 100644 --- a/src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java +++ b/src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java @@ -6,6 +6,8 @@ import java.util.Optional; import java.util.stream.Collectors; +import javafx.beans.InvalidationListener; +import javafx.beans.WeakInvalidationListener; import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanBinding; import javafx.beans.binding.IntegerBinding; @@ -23,6 +25,7 @@ import org.jabref.gui.icon.JabRefIcon; import org.jabref.gui.util.BackgroundTask; import org.jabref.gui.util.CustomLocalDragboard; +import org.jabref.gui.util.DefaultTaskExecutor; import org.jabref.gui.util.DroppingMouseLocation; import org.jabref.gui.util.TaskExecutor; import org.jabref.logic.groups.DefaultGroupsFactory; @@ -34,6 +37,7 @@ import org.jabref.model.groups.AutomaticGroup; import org.jabref.model.groups.GroupEntryChanger; import org.jabref.model.groups.GroupTreeNode; +import org.jabref.model.groups.TexGroup; import org.jabref.model.strings.StringUtil; import org.jabref.preferences.PreferencesService; @@ -59,6 +63,7 @@ public class GroupNodeViewModel { private final CustomLocalDragboard localDragBoard; private final ObservableList entriesList; private final PreferencesService preferencesService; + private final InvalidationListener onInvalidatedGroup = (listener) -> refreshGroup(); public GroupNodeViewModel(BibDatabaseContext databaseContext, StateManager stateManager, TaskExecutor taskExecutor, GroupTreeNode groupNode, CustomLocalDragboard localDragBoard, PreferencesService preferencesService) { this.databaseContext = Objects.requireNonNull(databaseContext); @@ -81,6 +86,9 @@ public GroupNodeViewModel(BibDatabaseContext databaseContext, StateManager state } else { children = EasyBind.mapBacked(groupNode.getChildren(), this::toViewModel); } + if (groupNode.getGroup() instanceof TexGroup) { + databaseContext.getMetaData().groupsBinding().addListener(new WeakInvalidationListener(onInvalidatedGroup)); + } hasChildren = new SimpleBooleanProperty(); hasChildren.bind(Bindings.isNotEmpty(children)); updateMatchedEntries(); @@ -250,6 +258,17 @@ private void onDatabaseChanged(ListChangeListener.Change cha } } + private void refreshGroup() { + DefaultTaskExecutor.runInJavaFXThread(() -> { + updateMatchedEntries(); // Update the entries matched by the group + // "Re-add" to the selected groups if it were selected, this refreshes the entries the user views + ObservableList selectedGroups = this.stateManager.getSelectedGroup(this.databaseContext); + if (selectedGroups.remove(this.groupNode)) { + selectedGroups.add(this.groupNode); + } + }); + } + private void updateMatchedEntries() { // We calculate the new hit value // We could be more intelligent and try to figure out the new number of hits based on the entry change @@ -290,10 +309,11 @@ public Optional getChildByPath(String pathToSource) { } /** - * Decides if the content stored in the given {@link Dragboard} can be droped on the given target row. - * Currently, the following sources are allowed: - * - another group (will be added as subgroup on drop) - * - entries if the group implements {@link GroupEntryChanger} (will be assigned to group on drop) + * Decides if the content stored in the given {@link Dragboard} can be dropped on the given target row. Currently, the following sources are allowed: + *
    + *
  • another group (will be added as subgroup on drop)
  • + *
  • entries if the group implements {@link GroupEntryChanger} (will be assigned to group on drop)
  • + *
*/ public boolean acceptableDrop(Dragboard dragboard) { // TODO: we should also check isNodeDescendant @@ -343,15 +363,9 @@ public void draggedOn(GroupNodeViewModel target, DroppingMouseLocation mouseLoca // Bottom + top -> insert source row before / after this row // Center -> add as child switch (mouseLocation) { - case BOTTOM: - this.moveTo(targetParent.get(), targetIndex + 1); - break; - case CENTER: - this.moveTo(target); - break; - case TOP: - this.moveTo(targetParent.get(), targetIndex); - break; + case BOTTOM -> this.moveTo(targetParent.get(), targetIndex + 1); + case CENTER -> this.moveTo(target); + case TOP -> this.moveTo(targetParent.get(), targetIndex); } } else { // No parent = root -> just add diff --git a/src/main/java/org/jabref/gui/maintable/BibEntryTableViewModel.java b/src/main/java/org/jabref/gui/maintable/BibEntryTableViewModel.java index 75a72b4cdf3..a9dedaedb59 100644 --- a/src/main/java/org/jabref/gui/maintable/BibEntryTableViewModel.java +++ b/src/main/java/org/jabref/gui/maintable/BibEntryTableViewModel.java @@ -6,14 +6,16 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.function.Predicate; import java.util.stream.Collectors; import javafx.beans.Observable; +import javafx.beans.binding.Binding; import javafx.beans.binding.Bindings; -import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ObservableValue; import org.jabref.gui.specialfields.SpecialFieldValueViewModel; +import org.jabref.gui.util.uithreadaware.UiThreadBinding; import org.jabref.logic.importer.util.FileFieldParser; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; @@ -36,7 +38,7 @@ public class BibEntryTableViewModel { private final Map> specialFieldValues = new HashMap<>(); private final EasyBinding> linkedFiles; private final EasyBinding> linkedIdentifiers; - private final ObservableValue> matchedGroups; + private final Binding> matchedGroups; public BibEntryTableViewModel(BibEntry entry, BibDatabaseContext bibDatabaseContext, ObservableValue fieldValueFormatter) { this.entry = entry; @@ -67,19 +69,15 @@ public BibEntry getEntry() { return entry; } - private static ObservableValue> createMatchedGroupsBinding(BibDatabaseContext database, BibEntry entry) { - Optional root = database.getMetaData().getGroups(); - if (root.isPresent()) { - return EasyBind.map(entry.getFieldBinding(StandardField.GROUPS), field -> { - List groups = root.get().getMatchingGroups(entry) - .stream() - .map(GroupTreeNode::getGroup) - .collect(Collectors.toList()); - groups.remove(root.get().getGroup()); - return groups; - }); - } - return new SimpleObjectProperty<>(Collections.emptyList()); + private static Binding> createMatchedGroupsBinding(BibDatabaseContext database, BibEntry entry) { + return new UiThreadBinding<>(EasyBind.combine(entry.getFieldBinding(StandardField.GROUPS), database.getMetaData().groupsBinding(), + (a, b) -> + database.getMetaData().getGroups().map(groupTreeNode -> + groupTreeNode.getMatchingGroups(entry).stream() + .map(GroupTreeNode::getGroup) + .filter(Predicate.not(Predicate.isEqual(groupTreeNode.getGroup()))) + .collect(Collectors.toList())) + .orElse(Collections.emptyList()))); } public OptionalBinding getField(Field field) { @@ -119,7 +117,7 @@ public ObservableValue getFields(OrFields fields) { observables.add(fieldValueFormatter); value = Bindings.createStringBinding(() -> - fieldValueFormatter.getValue().formatFieldsValues(fields, entry), + fieldValueFormatter.getValue().formatFieldsValues(fields, entry), observables.toArray(Observable[]::new)); fieldValues.put(fields, value); return value; diff --git a/src/main/java/org/jabref/gui/preview/CopyCitationAction.java b/src/main/java/org/jabref/gui/preview/CopyCitationAction.java index 03d6a353536..b2e3be5eb80 100644 --- a/src/main/java/org/jabref/gui/preview/CopyCitationAction.java +++ b/src/main/java/org/jabref/gui/preview/CopyCitationAction.java @@ -31,8 +31,7 @@ import org.slf4j.LoggerFactory; /** - * Copies the selected entries and formats them with the selected citation style (or preview), then it is copied to the clipboard. - * This worker cannot be reused. + * Copies the selected entries and formats them with the selected citation style (or preview), then it is copied to the clipboard. This worker cannot be reused. */ public class CopyCitationAction extends SimpleCommand { @@ -93,8 +92,7 @@ private List generateCitations() throws IOException { } /** - * Generates a plain text string out of the preview and copies it additionally to the html to the clipboard - * (WYSIWYG Editors use the HTML, plain text editors the text) + * Generates a plain text string out of the preview and copies it additionally to the html to the clipboard (WYSIWYG Editors use the HTML, plain text editors the text) */ protected static ClipboardContent processPreview(List citations) { ClipboardContent content = new ClipboardContent(); @@ -182,23 +180,14 @@ private void setClipBoardContent(List citations) { // if it's generated by a citation style take care of each output format ClipboardContent content; switch (outputFormat) { - case HTML: - content = processHtml(citations); - break; - case RTF: - content = processRtf(citations); - break; - case XSL_FO: - content = processXslFo(citations); - break; - case ASCII_DOC: - case TEXT: - content = processText(citations); - break; - default: + case HTML -> content = processHtml(citations); + case RTF -> content = processRtf(citations); + case XSL_FO -> content = processXslFo(citations); + case ASCII_DOC, TEXT -> content = processText(citations); + default -> { LOGGER.warn("unknown output format: '" + outputFormat + "', processing it via the default."); content = processText(citations); - break; + } } clipBoardManager.setContent(content); } diff --git a/src/main/java/org/jabref/gui/util/BindingsHelper.java b/src/main/java/org/jabref/gui/util/BindingsHelper.java index d63f848e893..c4f88f1e15b 100644 --- a/src/main/java/org/jabref/gui/util/BindingsHelper.java +++ b/src/main/java/org/jabref/gui/util/BindingsHelper.java @@ -25,8 +25,7 @@ import com.tobiasdiez.easybind.Subscription; /** - * Helper methods for javafx binding. - * Some methods are taken from https://bugs.openjdk.java.net/browse/JDK-8134679 + * Helper methods for javafx binding. Some methods are taken from https://bugs.openjdk.java.net/browse/JDK-8134679 */ public class BindingsHelper { @@ -43,7 +42,7 @@ public static Subscription includePseudoClassWhen(Node node, PseudoClass pseudoC } public static ObservableList map(ObservableValue source, Function> mapper) { - PreboundBinding> binding = new PreboundBinding>(source) { + PreboundBinding> binding = new PreboundBinding<>(source) { @Override protected List computeValue() { @@ -57,7 +56,7 @@ protected List computeValue() { } /** - * Binds propertA bidirectional to propertyB using the provided map functions to convert between them. + * Binds propertyA bidirectional to propertyB using the provided map functions to convert between them. */ public static void bindBidirectional(Property propertyA, Property propertyB, Function mapAtoB, Function mapBtoA) { Consumer updateA = newValueB -> propertyA.setValue(mapBtoA.apply(newValueB)); @@ -66,15 +65,14 @@ public static void bindBidirectional(Property propertyA, Property p } /** - * Binds propertA bidirectional to propertyB while using updateB to update propertyB when propertyA changed. + * Binds propertyA bidirectional to propertyB while using updateB to update propertyB when propertyA changed. */ public static void bindBidirectional(Property propertyA, ObservableValue propertyB, Consumer updateB) { bindBidirectional(propertyA, propertyB, propertyA::setValue, updateB); } /** - * Binds propertA bidirectional to propertyB using updateB to update propertyB when propertyA changed and similar - * for updateA. + * Binds propertyA bidirectional to propertyB using updateB to update propertyB when propertyA changed and similar for updateA. */ public static void bindBidirectional(ObservableValue propertyA, ObservableValue propertyB, Consumer updateA, Consumer updateB) { final BidirectionalBinding binding = new BidirectionalBinding<>(propertyA, propertyB, updateA, updateB); @@ -135,7 +133,7 @@ public static void bindContentBidirectional(ObservableMap proper } public static ObservableValue constantOf(T value) { - return new ObjectBinding() { + return new ObjectBinding<>() { @Override protected T computeValue() { diff --git a/src/main/java/org/jabref/gui/util/uithreadaware/UiThreadBinding.java b/src/main/java/org/jabref/gui/util/uithreadaware/UiThreadBinding.java new file mode 100644 index 00000000000..f5c7fb5c55a --- /dev/null +++ b/src/main/java/org/jabref/gui/util/uithreadaware/UiThreadBinding.java @@ -0,0 +1,63 @@ +package org.jabref.gui.util.uithreadaware; + +import javafx.beans.InvalidationListener; +import javafx.beans.binding.Binding; +import javafx.beans.value.ChangeListener; +import javafx.collections.ObservableList; + +/** + * This class can be used to wrap a {@link Binding} inside it. When wrapped, any Listener listening for updates to the wrapped {@link Binding} (for example because of a binding to it) is ensured to be notified on the JavaFX Application Thread. It should be used to implement bindings where updates come in from a background thread but should be reflected in the UI where it is necessary that changes to the UI are performed on the JavaFX Application thread. + */ +public class UiThreadBinding implements Binding { + + private final Binding delegate; + + public UiThreadBinding(Binding delegate) { + this.delegate = delegate; + } + + @Override + public void addListener(InvalidationListener listener) { + delegate.addListener(new UiThreadInvalidationListener(listener)); + } + + @Override + public void removeListener(InvalidationListener listener) { + delegate.removeListener(listener); + } + + @Override + public void addListener(ChangeListener listener) { + delegate.addListener(new UiThreadChangeListener<>(listener)); + } + + @Override + public void removeListener(ChangeListener listener) { + delegate.removeListener(listener); + } + + @Override + public T getValue() { + return delegate.getValue(); + } + + @Override + public boolean isValid() { + return delegate.isValid(); + } + + @Override + public void invalidate() { + delegate.invalidate(); + } + + @Override + public ObservableList getDependencies() { + return delegate.getDependencies(); + } + + @Override + public void dispose() { + delegate.dispose(); + } +} diff --git a/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java b/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java index 3cc35a65be8..eb348459668 100644 --- a/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java +++ b/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java @@ -119,8 +119,7 @@ public Optional parseSingleEntry(String bibtexString) throws ParseExce } /** - * Will parse the BibTex-Data found when reading from reader. Ignores any encoding supplied in the file by - * "Encoding: myEncoding". + * Will parse the BibTex-Data found when reading from reader. Ignores any encoding supplied in the file by "Encoding: myEncoding". *

* The reader will be consumed. *

@@ -311,8 +310,7 @@ private void parseBibtexString() throws IOException { } /** - * Puts all text that has been read from the reader, including newlines, etc., since the last call of this method into a string. - * Removes the JabRef file header, if it is found + * Puts all text that has been read from the reader, including newlines, etc., since the last call of this method into a string. Removes the JabRef file header, if it is found * * @return the text read so far */ @@ -611,8 +609,7 @@ private String parseFieldContent(Field field) throws IOException { } /** - * This method is used to parse string labels, field names, entry type and - * numbers outside brackets. + * This method is used to parse string labels, field names, entry type and numbers outside brackets. */ private String parseTextToken() throws IOException { StringBuilder token = new StringBuilder(20); diff --git a/src/main/java/org/jabref/model/database/event/BibDatabaseContextChangedEvent.java b/src/main/java/org/jabref/model/database/event/BibDatabaseContextChangedEvent.java index 197097691e3..d0c993f6e42 100644 --- a/src/main/java/org/jabref/model/database/event/BibDatabaseContextChangedEvent.java +++ b/src/main/java/org/jabref/model/database/event/BibDatabaseContextChangedEvent.java @@ -19,6 +19,9 @@ public BibDatabaseContextChangedEvent(boolean filteredOut) { this.filteredOut = filteredOut; } + /** + * Check if this event can be filtered out to be synchronized with a database at a later time. + */ public boolean isFilteredOut() { return filteredOut; } diff --git a/src/main/java/org/jabref/model/groups/GroupTreeNode.java b/src/main/java/org/jabref/model/groups/GroupTreeNode.java index ded75ec4ef1..6cb8b15d722 100644 --- a/src/main/java/org/jabref/model/groups/GroupTreeNode.java +++ b/src/main/java/org/jabref/model/groups/GroupTreeNode.java @@ -21,7 +21,7 @@ */ public class GroupTreeNode extends TreeNode { - private static final String PATH_DELEMITER = " > "; + private static final String PATH_DELIMITER = " > "; private AbstractGroup group; /** @@ -31,7 +31,7 @@ public class GroupTreeNode extends TreeNode { */ public GroupTreeNode(AbstractGroup group) { super(GroupTreeNode.class); - this.group = Objects.requireNonNull(group); + setGroup(group, false, false, null); } public static GroupTreeNode fromGroup(AbstractGroup group) { @@ -59,18 +59,17 @@ public void setGroup(AbstractGroup newGroup) { } /** - * Associates the specified group with this node while also providing the possibility to modify previous matched - * entries so that they are now matched by the new group. + * Associates the specified group with this node while also providing the possibility to modify previous matched entries so that they are now matched by the new group. * - * @param newGroup the new group (has to be non-null) - * @param shouldKeepPreviousAssignments specifies whether previous matched entries should be added to the new group + * @param newGroup the new group (has to be non-null) + * @param shouldKeepPreviousAssignments specifies whether previous matched entries should be added to the new group * @param shouldRemovePreviousAssignments specifies whether previous matched entries should be removed from the old group - * @param entriesInDatabase list of entries in the database + * @param entriesInDatabase list of entries in the database */ public List setGroup(AbstractGroup newGroup, boolean shouldKeepPreviousAssignments, boolean shouldRemovePreviousAssignments, List entriesInDatabase) { AbstractGroup oldGroup = getGroup(); - setGroup(newGroup); + group = Objects.requireNonNull(newGroup); List changes = new ArrayList<>(); boolean shouldRemove = shouldRemovePreviousAssignments && (oldGroup instanceof GroupEntryChanger); @@ -92,11 +91,7 @@ public List setGroup(AbstractGroup newGroup, boolean shouldKeepPrev } /** - * Creates a {@link SearchMatcher} that matches entries of this group and that takes the hierarchical information - * into account. I.e., it finds elements contained in this nodes group, - * or the union of those elements in its own group and its - * children's groups (recursively), or the intersection of the elements in - * its own group and its parent's group (depending on the hierarchical settings stored in the involved groups) + * Creates a {@link SearchMatcher} that matches entries of this group and that takes the hierarchical information into account. I.e., it finds elements contained in this nodes group, or the union of those elements in its own group and its children's groups (recursively), or the intersection of the elements in its own group and its parent's group (depending on the hierarchical settings stored in the involved groups) */ public SearchMatcher getSearchMatcher() { return getSearchMatcher(group.getHierarchicalContext()); @@ -241,15 +236,14 @@ public List findMatches(BibDatabase database) { } /** - * Returns whether this group matches the specified {@link BibEntry} while taking the hierarchical information - * into account. + * Returns whether this group matches the specified {@link BibEntry} while taking the hierarchical information into account. */ public boolean matches(BibEntry entry) { return getSearchMatcher().isMatch(entry); } /** - * Get the path from the root of the tree as a string (every group name is separated by {@link #PATH_DELEMITER}. + * Get the path from the root of the tree as a string (every group name is separated by {@link #PATH_DELIMITER}. *

* The name of the root is not included. */ @@ -257,7 +251,7 @@ public String getPath() { return getPathFromRoot().stream() .skip(1) // Skip root .map(GroupTreeNode::getName) - .collect(Collectors.joining(PATH_DELEMITER)); + .collect(Collectors.joining(PATH_DELIMITER)); } @Override @@ -268,14 +262,13 @@ public String toString() { } /** - * Finds a children using the given path. - * Each group name should be separated by {@link #PATH_DELEMITER}. - * + * Finds a children using the given path. Each group name should be separated by {@link #PATH_DELIMITER}. + *

* The path should be generated using {@link #getPath()}. */ public Optional getChildByPath(String pathToSource) { GroupTreeNode present = this; - for (String groupName : pathToSource.split(PATH_DELEMITER)) { + for (String groupName : pathToSource.split(PATH_DELIMITER)) { Optional childWithName = present .getChildren().stream() .filter(group -> Objects.equals(group.getName(), groupName)) @@ -292,9 +285,7 @@ public Optional getChildByPath(String pathToSource) { } /** - * Adds the specified entries to this group. - * If the group does not support explicit adding of entries (i.e., does not implement {@link GroupEntryChanger}), - * then no action is performed. + * Adds the specified entries to this group. If the group does not support explicit adding of entries (i.e., does not implement {@link GroupEntryChanger}), then no action is performed. */ public List addEntriesToGroup(Collection entries) { if (getGroup() instanceof GroupEntryChanger) { @@ -305,8 +296,7 @@ public List addEntriesToGroup(Collection entries) { } /** - * Removes the given entries from this group. If the group does not support the explicit removal of entries (i.e., - * does not implement {@link GroupEntryChanger}), then no action is performed. + * Removes the given entries from this group. If the group does not support the explicit removal of entries (i.e., does not implement {@link GroupEntryChanger}), then no action is performed. */ public List removeEntriesFromGroup(List entries) { if (getGroup() instanceof GroupEntryChanger) { diff --git a/src/main/java/org/jabref/model/groups/TexGroup.java b/src/main/java/org/jabref/model/groups/TexGroup.java index b50c6ec8259..725a180c448 100644 --- a/src/main/java/org/jabref/model/groups/TexGroup.java +++ b/src/main/java/org/jabref/model/groups/TexGroup.java @@ -3,7 +3,7 @@ import java.io.IOException; import java.net.InetAddress; import java.nio.file.Path; -import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Set; @@ -26,7 +26,7 @@ public class TexGroup extends AbstractGroup implements FileUpdateListener { private static final Logger LOGGER = LoggerFactory.getLogger(TexGroup.class); private final Path filePath; - private Set keysUsedInAux = null; + private Set keysUsedInAux; private final FileUpdateMonitor fileMonitor; private final AuxParser auxParser; private final MetaData metaData; @@ -119,6 +119,7 @@ public Path getFilePath() { public void fileUpdated() { // Reset previous parse result keysUsedInAux = null; + metaData.groupsBinding().invalidate(); } private Path relativize(Path path) { @@ -132,11 +133,8 @@ private Path expandPath(Path path) { } private List getFileDirectoriesAsPaths() { - List fileDirs = new ArrayList<>(); - - metaData.getLatexFileDirectory(user) - .ifPresent(fileDirs::add); - - return fileDirs; + return metaData.getLatexFileDirectory(user) + .map(List::of) + .orElse(Collections.emptyList()); } } diff --git a/src/main/java/org/jabref/model/metadata/MetaData.java b/src/main/java/org/jabref/model/metadata/MetaData.java index cf6008e4ecb..238658803d3 100644 --- a/src/main/java/org/jabref/model/metadata/MetaData.java +++ b/src/main/java/org/jabref/model/metadata/MetaData.java @@ -9,6 +9,9 @@ import java.util.Objects; import java.util.Optional; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; + import org.jabref.architecture.AllowedToUseLogic; import org.jabref.logic.citationkeypattern.AbstractCitationKeyPattern; import org.jabref.logic.citationkeypattern.DatabaseCitationKeyPattern; @@ -23,6 +26,8 @@ import org.jabref.model.metadata.event.MetaDataChangedEvent; import com.google.common.eventbus.EventBus; +import com.tobiasdiez.easybind.optional.OptionalBinding; +import com.tobiasdiez.easybind.optional.OptionalWrapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -51,7 +56,8 @@ public class MetaData { private final Map citeKeyPatterns = new HashMap<>(); // private final Map userFileDirectory = new HashMap<>(); // private final Map laTexFileDirectory = new HashMap<>(); // - private GroupTreeNode groupsRoot; + private final ObjectProperty groupsRoot = new SimpleObjectProperty<>(null); + private final OptionalBinding groupsRootBinding = new OptionalWrapper<>(groupsRoot); private Charset encoding; private SaveOrderConfig saveOrderConfig; private String defaultCiteKeyPattern; @@ -60,7 +66,7 @@ public class MetaData { private boolean isProtected; private String defaultFileDirectory; private final ContentSelectors contentSelectors = new ContentSelectors(); - private final Map> unkownMetaData = new HashMap<>(); + private final Map> unknownMetaData = new HashMap<>(); private boolean isEventPropagationEnabled = true; /** @@ -80,16 +86,21 @@ public void setSaveOrderConfig(SaveOrderConfig saveOrderConfig) { } public Optional getGroups() { - return Optional.ofNullable(groupsRoot); + return groupsRootBinding.getValue(); + } + + public OptionalBinding groupsBinding() { + return groupsRootBinding; } /** - * Sets a new group root node. WARNING : This invalidates everything - * returned by getGroups() so far!!! + * Sets a new group root node. WARNING : This invalidates everything returned by getGroups() so far!!! */ public void setGroups(GroupTreeNode root) { - groupsRoot = Objects.requireNonNull(root); - groupsRoot.subscribeToDescendantChanged(groupTreeNode -> eventBus.post(new GroupUpdatedEvent(this))); + Objects.requireNonNull(root); + groupsRoot.setValue(root); + root.subscribeToDescendantChanged(groupTreeNode -> groupsRootBinding.invalidate()); + root.subscribeToDescendantChanged(groupTreeNode -> eventBus.post(new GroupUpdatedEvent(this))); eventBus.post(new GroupUpdatedEvent(this)); postChange(); } @@ -111,8 +122,7 @@ public AbstractCitationKeyPattern getCiteKeyPattern(GlobalCitationKeyPattern glo /** * Updates the stored key patterns to the given key patterns. * - * @param bibtexKeyPattern the key patterns to update to.
A reference to this object is stored internally and - * is returned at getCiteKeyPattern(); + * @param bibtexKeyPattern the key patterns to update to.
A reference to this object is stored internally and is returned at getCiteKeyPattern(); */ public void setCiteKeyPattern(AbstractCitationKeyPattern bibtexKeyPattern) { Objects.requireNonNull(bibtexKeyPattern); @@ -317,14 +327,14 @@ public Map getLatexFileDirectories() { } public Map> getUnknownMetaData() { - return Collections.unmodifiableMap(unkownMetaData); + return Collections.unmodifiableMap(unknownMetaData); } public void putUnknownMetaDataItem(String key, List value) { Objects.requireNonNull(key); Objects.requireNonNull(value); - unkownMetaData.put(key, value); + unknownMetaData.put(key, value); } @Override @@ -337,7 +347,7 @@ public boolean equals(Object o) { } MetaData metaData = (MetaData) o; return (isProtected == metaData.isProtected) - && Objects.equals(groupsRoot, metaData.groupsRoot) + && Objects.equals(groupsRoot.getValue(), metaData.groupsRoot.getValue()) && Objects.equals(encoding, metaData.encoding) && Objects.equals(saveOrderConfig, metaData.saveOrderConfig) && Objects.equals(citeKeyPatterns, metaData.citeKeyPatterns) @@ -352,7 +362,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(groupsRoot, encoding, saveOrderConfig, citeKeyPatterns, userFileDirectory, + return Objects.hash(groupsRoot.getValue(), encoding, saveOrderConfig, citeKeyPatterns, userFileDirectory, defaultCiteKeyPattern, saveActions, mode, isProtected, defaultFileDirectory); } } diff --git a/src/test/java/org/jabref/logic/citationkeypattern/BracketedPatternTest.java b/src/test/java/org/jabref/logic/citationkeypattern/BracketedPatternTest.java index d42754fef16..bbc0e6eb3ea 100644 --- a/src/test/java/org/jabref/logic/citationkeypattern/BracketedPatternTest.java +++ b/src/test/java/org/jabref/logic/citationkeypattern/BracketedPatternTest.java @@ -280,7 +280,7 @@ void expandBracketsWithAuthorStartingWithBrackets() { } @Test - void expandBracketsWithModifierContainingRegexCharacterCkass() { + void expandBracketsWithModifierContainingRegexCharacterClass() { BibEntry bibEntry = new BibEntry().withField(StandardField.TITLE, "Wickedness:Managing"); assertEquals("Wickedness.Managing", BracketedPattern.expandBrackets("[title:regex(\"[:]+\",\".\")]", null, bibEntry, null));