From 974e625fd412cdd281c62b5558050b7abcfd1be4 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Sun, 22 Oct 2017 20:55:20 +0800 Subject: [PATCH 01/22] Small code improvements --- src/main/java/org/jabref/collab/ChangeScanner.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/jabref/collab/ChangeScanner.java b/src/main/java/org/jabref/collab/ChangeScanner.java index 855ce8afe9c..3d3a75513c7 100644 --- a/src/main/java/org/jabref/collab/ChangeScanner.java +++ b/src/main/java/org/jabref/collab/ChangeScanner.java @@ -162,12 +162,12 @@ private void storeTempDatabase() { } private void scanMetaData(MetaData inMemory, MetaData onTmp, MetaData onDisk) { - if (!onTmp.isEmpty()) { - if (!inMemory.equals(onDisk)) { + if (onTmp.isEmpty()) { + if (!onDisk.isEmpty() || !onTmp.equals(onDisk)) { changes.add(new MetaDataChange(inMemory, onDisk)); } } else { - if (!onDisk.isEmpty() || !onTmp.equals(onDisk)) { + if (!inMemory.equals(onDisk)) { changes.add(new MetaDataChange(inMemory, onDisk)); } } @@ -320,12 +320,12 @@ private void scanPreamble(BibDatabase inMemory, BibDatabase onTmp, BibDatabase o String mem = inMemory.getPreamble().orElse(null); Optional tmp = onTmp.getPreamble(); Optional disk = onDisk.getPreamble(); - if (!tmp.isPresent()) { - disk.ifPresent(diskContent -> changes.add(new PreambleChange(mem, diskContent))); - } else { + if (tmp.isPresent()) { if (!disk.isPresent() || !tmp.equals(disk)) { changes.add(new PreambleChange(mem, disk.orElse(null))); } + } else { + disk.ifPresent(diskContent -> changes.add(new PreambleChange(mem, diskContent))); } } @@ -455,7 +455,7 @@ private void scanGroups(MetaData inTemp, MetaData onDisk) { if (!groupsTmp.isPresent() && !groupsDisk.isPresent()) { return; } - if ((groupsTmp.isPresent() && !groupsDisk.isPresent()) || !groupsTmp.isPresent()) { + if (!groupsTmp.isPresent() || !groupsDisk.isPresent()) { changes.add(new GroupChange(groupsDisk.orElse(null), groupsTmp.orElse(null))); return; } From fc588d716405c3d7df953be38bf5dd0bdb84a136 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Mon, 23 Oct 2017 23:15:56 +0800 Subject: [PATCH 02/22] Move existing code to gui and rename change classes to view model --- src/main/java/org/jabref/gui/BasePanel.java | 4 +-- .../{ => gui}/collab/ChangeDisplayDialog.java | 12 ++++---- .../{ => gui}/collab/ChangeScanner.java | 30 +++++++++---------- .../collab/ChangeViewModel.java} | 12 ++++---- .../collab/DatabaseChangeMonitor.java | 2 +- .../collab/EntryAddChangeViewModel.java} | 6 ++-- .../collab/EntryChangeViewModel.java} | 20 ++++++------- .../collab/EntryDeleteChangeViewModel.java} | 8 ++--- .../{ => gui}/collab/FileUpdatePanel.java | 2 +- .../collab/GroupChangeViewModel.java} | 6 ++-- .../org/jabref/{ => gui}/collab/InfoPane.java | 2 +- .../collab/MetaDataChangeViewModel.java} | 6 ++-- .../collab/PreambleChangeViewModel.java} | 6 ++-- .../collab/StringAddChangeViewModel.java} | 8 ++--- .../collab/StringChangeViewModel.java} | 8 ++--- .../collab/StringNameChangeViewModel.java} | 10 +++---- .../collab/StringRemoveChangeViewModel.java} | 8 ++--- .../gui/exporter/SaveDatabaseAction.java | 4 +-- 18 files changed, 77 insertions(+), 77 deletions(-) rename src/main/java/org/jabref/{ => gui}/collab/ChangeDisplayDialog.java (94%) rename src/main/java/org/jabref/{ => gui}/collab/ChangeScanner.java (93%) rename src/main/java/org/jabref/{collab/Change.java => gui/collab/ChangeViewModel.java} (88%) rename src/main/java/org/jabref/{ => gui}/collab/DatabaseChangeMonitor.java (99%) rename src/main/java/org/jabref/{collab/EntryAddChange.java => gui/collab/EntryAddChangeViewModel.java} (88%) rename src/main/java/org/jabref/{collab/EntryChange.java => gui/collab/EntryChangeViewModel.java} (88%) rename src/main/java/org/jabref/{collab/EntryDeleteChange.java => gui/collab/EntryDeleteChangeViewModel.java} (90%) rename src/main/java/org/jabref/{ => gui}/collab/FileUpdatePanel.java (99%) rename src/main/java/org/jabref/{collab/GroupChange.java => gui/collab/GroupChangeViewModel.java} (93%) rename src/main/java/org/jabref/{ => gui}/collab/InfoPane.java (94%) rename src/main/java/org/jabref/{collab/MetaDataChange.java => gui/collab/MetaDataChangeViewModel.java} (89%) rename src/main/java/org/jabref/{collab/PreambleChange.java => gui/collab/PreambleChangeViewModel.java} (91%) rename src/main/java/org/jabref/{collab/StringAddChange.java => gui/collab/StringAddChangeViewModel.java} (92%) rename src/main/java/org/jabref/{collab/StringChange.java => gui/collab/StringChangeViewModel.java} (93%) rename src/main/java/org/jabref/{collab/StringNameChange.java => gui/collab/StringNameChangeViewModel.java} (89%) rename src/main/java/org/jabref/{collab/StringRemoveChange.java => gui/collab/StringRemoveChangeViewModel.java} (89%) diff --git a/src/main/java/org/jabref/gui/BasePanel.java b/src/main/java/org/jabref/gui/BasePanel.java index 1fcec7a9728..2902f63ce11 100644 --- a/src/main/java/org/jabref/gui/BasePanel.java +++ b/src/main/java/org/jabref/gui/BasePanel.java @@ -43,8 +43,6 @@ import org.jabref.Globals; import org.jabref.JabRefExecutorService; -import org.jabref.collab.DatabaseChangeMonitor; -import org.jabref.collab.FileUpdatePanel; import org.jabref.gui.actions.Actions; import org.jabref.gui.actions.BaseAction; import org.jabref.gui.actions.CleanupAction; @@ -54,6 +52,8 @@ import org.jabref.gui.autocompleter.PersonNameSuggestionProvider; import org.jabref.gui.autocompleter.SuggestionProviders; import org.jabref.gui.bibtexkeypattern.SearchFixDuplicateLabels; +import org.jabref.gui.collab.DatabaseChangeMonitor; +import org.jabref.gui.collab.FileUpdatePanel; import org.jabref.gui.contentselector.ContentSelectorDialog; import org.jabref.gui.desktop.JabRefDesktop; import org.jabref.gui.entryeditor.EntryEditor; diff --git a/src/main/java/org/jabref/collab/ChangeDisplayDialog.java b/src/main/java/org/jabref/gui/collab/ChangeDisplayDialog.java similarity index 94% rename from src/main/java/org/jabref/collab/ChangeDisplayDialog.java rename to src/main/java/org/jabref/gui/collab/ChangeDisplayDialog.java index 0706dbdde9d..1bf8978f1f0 100644 --- a/src/main/java/org/jabref/collab/ChangeDisplayDialog.java +++ b/src/main/java/org/jabref/gui/collab/ChangeDisplayDialog.java @@ -1,4 +1,4 @@ -package org.jabref.collab; +package org.jabref.gui.collab; import java.awt.BorderLayout; import java.awt.Insets; @@ -31,7 +31,7 @@ class ChangeDisplayDialog extends JabRefDialog implements TreeSelectionListener private final JPanel infoPanel = new JPanel(); private final JCheckBox cb = new JCheckBox(Localization.lang("Accept change")); private final JLabel rootInfo = new JLabel(Localization.lang("Select the tree nodes to view and accept or reject changes") + '.'); - private Change selected; + private ChangeViewModel selected; private JComponent infoShown; private boolean okPressed; @@ -86,9 +86,9 @@ public ChangeDisplayDialog(JFrame owner, final BasePanel panel, // Perform all accepted changes: // Store all edits in an Undoable object: NamedCompound ce = new NamedCompound(Localization.lang("Merged external changes")); - Enumeration enumer = root.children(); + Enumeration enumer = root.children(); boolean anyDisabled = false; - for (Change c : Collections.list(enumer)) { + for (ChangeViewModel c : Collections.list(enumer)) { boolean allAccepted = false; if (c.isAcceptable() && c.isAccepted()) { allAccepted = c.makeChange(panel, localSecondary, ce); @@ -133,8 +133,8 @@ private void setInfo(JComponent comp) { @Override public void valueChanged(TreeSelectionEvent e) { Object o = tree.getLastSelectedPathComponent(); - if (o instanceof Change) { - selected = (Change) o; + if (o instanceof ChangeViewModel) { + selected = (ChangeViewModel) o; setInfo(selected.description()); cb.setSelected(selected.isAccepted()); cb.setEnabled(selected.isAcceptable()); diff --git a/src/main/java/org/jabref/collab/ChangeScanner.java b/src/main/java/org/jabref/gui/collab/ChangeScanner.java similarity index 93% rename from src/main/java/org/jabref/collab/ChangeScanner.java rename to src/main/java/org/jabref/gui/collab/ChangeScanner.java index 3d3a75513c7..bf84b9a6e0c 100644 --- a/src/main/java/org/jabref/collab/ChangeScanner.java +++ b/src/main/java/org/jabref/gui/collab/ChangeScanner.java @@ -1,4 +1,4 @@ -package org.jabref.collab; +package org.jabref.gui.collab; import java.io.File; import java.io.IOException; @@ -164,11 +164,11 @@ private void storeTempDatabase() { private void scanMetaData(MetaData inMemory, MetaData onTmp, MetaData onDisk) { if (onTmp.isEmpty()) { if (!onDisk.isEmpty() || !onTmp.equals(onDisk)) { - changes.add(new MetaDataChange(inMemory, onDisk)); + changes.add(new MetaDataChangeViewModel(inMemory, onDisk)); } } else { if (!inMemory.equals(onDisk)) { - changes.add(new MetaDataChange(inMemory, onDisk)); + changes.add(new MetaDataChangeViewModel(inMemory, onDisk)); } } } @@ -258,11 +258,11 @@ private void scanEntries(EntrySorter memorySorter, EntrySorter tmpSorter, EntryS used.add(String.valueOf(bestMatchI)); it.remove(); - changes.add(new EntryChange(bestFit(tmpSorter, memorySorter, piv1), tmpSorter.getEntryAt(piv1), + changes.add(new EntryChangeViewModel(bestFit(tmpSorter, memorySorter, piv1), tmpSorter.getEntryAt(piv1), diskSorter.getEntryAt(bestMatchI))); } else { changes.add( - new EntryDeleteChange(bestFit(tmpSorter, memorySorter, piv1), tmpSorter.getEntryAt(piv1))); + new EntryDeleteChangeViewModel(bestFit(tmpSorter, memorySorter, piv1), tmpSorter.getEntryAt(piv1))); } } @@ -284,7 +284,7 @@ private void scanEntries(EntrySorter memorySorter, EntrySorter tmpSorter, EntryS } } if (!hasAlready) { - changes.add(new EntryAddChange(diskSorter.getEntryAt(i))); + changes.add(new EntryAddChangeViewModel(diskSorter.getEntryAt(i))); } } } @@ -322,10 +322,10 @@ private void scanPreamble(BibDatabase inMemory, BibDatabase onTmp, BibDatabase o Optional disk = onDisk.getPreamble(); if (tmp.isPresent()) { if (!disk.isPresent() || !tmp.equals(disk)) { - changes.add(new PreambleChange(mem, disk.orElse(null))); + changes.add(new PreambleChangeViewModel(mem, disk.orElse(null))); } } else { - disk.ifPresent(diskContent -> changes.add(new PreambleChange(mem, diskContent))); + disk.ifPresent(diskContent -> changes.add(new PreambleChangeViewModel(mem, diskContent))); } } @@ -352,10 +352,10 @@ private void scanStrings(BibDatabase inMem1, BibDatabase inTmp, BibDatabase onDi // But they have nonmatching contents, so we've found a change. Optional mem = findString(inMem1, tmp.getName(), usedInMem); if (mem.isPresent()) { - changes.add(new StringChange(mem.get(), tmp, tmp.getName(), mem.get().getContent(), + changes.add(new StringChangeViewModel(mem.get(), tmp, tmp.getName(), mem.get().getContent(), disk.getContent())); } else { - changes.add(new StringChange(null, tmp, tmp.getName(), null, disk.getContent())); + changes.add(new StringChangeViewModel(null, tmp, tmp.getName(), null, disk.getContent())); } } used.add(diskId); @@ -398,7 +398,7 @@ private void scanStrings(BibDatabase inMem1, BibDatabase inTmp, BibDatabase onDi } if (bsMem != null) { - changes.add(new StringNameChange(bsMem, tmp, bsMem.getName(), tmp.getName(), + changes.add(new StringNameChangeViewModel(bsMem, tmp, bsMem.getName(), tmp.getName(), disk.getName(), tmp.getContent())); i.remove(); used.add(diskId); @@ -416,7 +416,7 @@ private void scanStrings(BibDatabase inMem1, BibDatabase inTmp, BibDatabase onDi BibtexString tmp = inTmp.getString(notMatchedId); // The removed string is not removed from the mem version. findString(inMem1, tmp.getName(), usedInMem).ifPresent( - x -> changes.add(new StringRemoveChange(tmp, tmp, x))); + x -> changes.add(new StringRemoveChangeViewModel(tmp, tmp, x))); } } @@ -426,7 +426,7 @@ private void scanStrings(BibDatabase inMem1, BibDatabase inTmp, BibDatabase onDi if (!used.contains(diskId)) { BibtexString disk = onDisk.getString(diskId); used.add(diskId); - changes.add(new StringAddChange(disk)); + changes.add(new StringAddChangeViewModel(disk)); } } } @@ -456,12 +456,12 @@ private void scanGroups(MetaData inTemp, MetaData onDisk) { return; } if (!groupsTmp.isPresent() || !groupsDisk.isPresent()) { - changes.add(new GroupChange(groupsDisk.orElse(null), groupsTmp.orElse(null))); + changes.add(new GroupChangeViewModel(groupsDisk.orElse(null), groupsTmp.orElse(null))); return; } // Both present here if (!groupsTmp.equals(groupsDisk)) { - changes.add(new GroupChange(groupsDisk.get(), groupsTmp.get())); + changes.add(new GroupChangeViewModel(groupsDisk.get(), groupsTmp.get())); } } diff --git a/src/main/java/org/jabref/collab/Change.java b/src/main/java/org/jabref/gui/collab/ChangeViewModel.java similarity index 88% rename from src/main/java/org/jabref/collab/Change.java rename to src/main/java/org/jabref/gui/collab/ChangeViewModel.java index 077364edce1..0561a799b24 100644 --- a/src/main/java/org/jabref/collab/Change.java +++ b/src/main/java/org/jabref/gui/collab/ChangeViewModel.java @@ -1,4 +1,4 @@ -package org.jabref.collab; +package org.jabref.gui.collab; import javax.swing.JComponent; import javax.swing.tree.DefaultMutableTreeNode; @@ -7,17 +7,17 @@ import org.jabref.gui.undo.NamedCompound; import org.jabref.model.database.BibDatabase; -abstract class Change extends DefaultMutableTreeNode { +abstract class ChangeViewModel extends DefaultMutableTreeNode { protected String name; private boolean accepted = true; - Change() { + ChangeViewModel() { name = ""; } - Change(String name) { + ChangeViewModel(String name) { this.name = name; } @@ -40,8 +40,8 @@ public void setAccepted(boolean a) { * @return boolean false if the parent overrides by not being accepted. */ public boolean isAcceptable() { - if ((getParent() != null) && (getParent() instanceof Change)) { - return ((Change) getParent()).isAccepted(); + if ((getParent() != null) && (getParent() instanceof ChangeViewModel)) { + return ((ChangeViewModel) getParent()).isAccepted(); } else { return true; } diff --git a/src/main/java/org/jabref/collab/DatabaseChangeMonitor.java b/src/main/java/org/jabref/gui/collab/DatabaseChangeMonitor.java similarity index 99% rename from src/main/java/org/jabref/collab/DatabaseChangeMonitor.java rename to src/main/java/org/jabref/gui/collab/DatabaseChangeMonitor.java index bce1af7d10d..e4ba4845642 100644 --- a/src/main/java/org/jabref/collab/DatabaseChangeMonitor.java +++ b/src/main/java/org/jabref/gui/collab/DatabaseChangeMonitor.java @@ -1,4 +1,4 @@ -package org.jabref.collab; +package org.jabref.gui.collab; import java.io.IOException; import java.nio.file.Files; diff --git a/src/main/java/org/jabref/collab/EntryAddChange.java b/src/main/java/org/jabref/gui/collab/EntryAddChangeViewModel.java similarity index 88% rename from src/main/java/org/jabref/collab/EntryAddChange.java rename to src/main/java/org/jabref/gui/collab/EntryAddChangeViewModel.java index 234d2dd0c06..0cf40cc1f05 100644 --- a/src/main/java/org/jabref/collab/EntryAddChange.java +++ b/src/main/java/org/jabref/gui/collab/EntryAddChangeViewModel.java @@ -1,4 +1,4 @@ -package org.jabref.collab; +package org.jabref.gui.collab; import javax.swing.JComponent; import javax.swing.JScrollPane; @@ -12,13 +12,13 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.IdGenerator; -class EntryAddChange extends Change { +class EntryAddChangeViewModel extends ChangeViewModel { private final BibEntry diskEntry; private final JScrollPane sp; - public EntryAddChange(BibEntry diskEntry) { + public EntryAddChangeViewModel(BibEntry diskEntry) { super(Localization.lang("Added entry")); this.diskEntry = diskEntry; diff --git a/src/main/java/org/jabref/collab/EntryChange.java b/src/main/java/org/jabref/gui/collab/EntryChangeViewModel.java similarity index 88% rename from src/main/java/org/jabref/collab/EntryChange.java rename to src/main/java/org/jabref/gui/collab/EntryChangeViewModel.java index 7efd5b41126..fa11e45d318 100644 --- a/src/main/java/org/jabref/collab/EntryChange.java +++ b/src/main/java/org/jabref/gui/collab/EntryChangeViewModel.java @@ -1,4 +1,4 @@ -package org.jabref.collab; +package org.jabref.gui.collab; import java.util.Collections; import java.util.Enumeration; @@ -21,11 +21,11 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -class EntryChange extends Change { +class EntryChangeViewModel extends ChangeViewModel { - private static final Log LOGGER = LogFactory.getLog(EntryChange.class); + private static final Log LOGGER = LogFactory.getLog(EntryChangeViewModel.class); - public EntryChange(BibEntry memEntry, BibEntry tmpEntry, BibEntry diskEntry) { + public EntryChangeViewModel(BibEntry memEntry, BibEntry tmpEntry, BibEntry diskEntry) { super(); Optional key = tmpEntry.getCiteKeyOptional(); if (key.isPresent()) { @@ -58,13 +58,13 @@ public EntryChange(BibEntry memEntry, BibEntry tmpEntry, BibEntry diskEntry) { if ((tmp.isPresent()) && (disk.isPresent())) { if (!tmp.equals(disk)) { // Modified externally. - add(new FieldChange(field, memEntry, tmpEntry, mem.orElse(null), tmp.get(), disk.get())); + add(new FieldChangeViewModel(field, memEntry, tmpEntry, mem.orElse(null), tmp.get(), disk.get())); } } else if (((!tmp.isPresent()) && (disk.isPresent()) && !disk.get().isEmpty()) || ((!disk.isPresent()) && (tmp.isPresent()) && !tmp.get().isEmpty() && (mem.isPresent()) && !mem.get().isEmpty())) { // Added externally. - add(new FieldChange(field, memEntry, tmpEntry, mem.orElse(null), tmp.orElse(null), + add(new FieldChangeViewModel(field, memEntry, tmpEntry, mem.orElse(null), tmp.orElse(null), disk.orElse(null))); } } @@ -74,8 +74,8 @@ public EntryChange(BibEntry memEntry, BibEntry tmpEntry, BibEntry diskEntry) { public boolean makeChange(BasePanel panel, BibDatabase secondary, NamedCompound undoEdit) { boolean allAccepted = true; - Enumeration e = children(); - for (Change c : Collections.list(e)) { + Enumeration e = children(); + for (ChangeViewModel c : Collections.list(e)) { if (c.isAcceptable() && c.isAccepted()) { c.makeChange(panel, secondary, undoEdit); } else { @@ -97,7 +97,7 @@ public JComponent description() { return new JLabel(name); } - static class FieldChange extends Change { + static class FieldChangeViewModel extends ChangeViewModel { private final BibEntry entry; private final BibEntry tmpEntry; @@ -108,7 +108,7 @@ static class FieldChange extends Change { private final JScrollPane sp = new JScrollPane(tp); - public FieldChange(String field, BibEntry memEntry, BibEntry tmpEntry, String inMem, String onTmp, String onDisk) { + public FieldChangeViewModel(String field, BibEntry memEntry, BibEntry tmpEntry, String inMem, String onTmp, String onDisk) { super(field); entry = memEntry; this.tmpEntry = tmpEntry; diff --git a/src/main/java/org/jabref/collab/EntryDeleteChange.java b/src/main/java/org/jabref/gui/collab/EntryDeleteChangeViewModel.java similarity index 90% rename from src/main/java/org/jabref/collab/EntryDeleteChange.java rename to src/main/java/org/jabref/gui/collab/EntryDeleteChangeViewModel.java index 758363e113f..0965177272a 100644 --- a/src/main/java/org/jabref/collab/EntryDeleteChange.java +++ b/src/main/java/org/jabref/gui/collab/EntryDeleteChangeViewModel.java @@ -1,4 +1,4 @@ -package org.jabref.collab; +package org.jabref.gui.collab; import javax.swing.JComponent; import javax.swing.JScrollPane; @@ -15,16 +15,16 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -class EntryDeleteChange extends Change { +class EntryDeleteChangeViewModel extends ChangeViewModel { - private static final Log LOGGER = LogFactory.getLog(EntryDeleteChange.class); + private static final Log LOGGER = LogFactory.getLog(EntryDeleteChangeViewModel.class); private final BibEntry memEntry; private final BibEntry tmpEntry; private final JScrollPane sp; - public EntryDeleteChange(BibEntry memEntry, BibEntry tmpEntry) { + public EntryDeleteChangeViewModel(BibEntry memEntry, BibEntry tmpEntry) { super(Localization.lang("Deleted entry")); this.memEntry = memEntry; this.tmpEntry = tmpEntry; diff --git a/src/main/java/org/jabref/collab/FileUpdatePanel.java b/src/main/java/org/jabref/gui/collab/FileUpdatePanel.java similarity index 99% rename from src/main/java/org/jabref/collab/FileUpdatePanel.java rename to src/main/java/org/jabref/gui/collab/FileUpdatePanel.java index cd502a03edc..b2e72606dec 100644 --- a/src/main/java/org/jabref/collab/FileUpdatePanel.java +++ b/src/main/java/org/jabref/gui/collab/FileUpdatePanel.java @@ -1,4 +1,4 @@ -package org.jabref.collab; +package org.jabref.gui.collab; import java.awt.BorderLayout; import java.awt.event.ActionEvent; diff --git a/src/main/java/org/jabref/collab/GroupChange.java b/src/main/java/org/jabref/gui/collab/GroupChangeViewModel.java similarity index 93% rename from src/main/java/org/jabref/collab/GroupChange.java rename to src/main/java/org/jabref/gui/collab/GroupChangeViewModel.java index 7b6f398dee2..ac3398f6ba7 100644 --- a/src/main/java/org/jabref/collab/GroupChange.java +++ b/src/main/java/org/jabref/gui/collab/GroupChangeViewModel.java @@ -1,4 +1,4 @@ -package org.jabref.collab; +package org.jabref.gui.collab; import javax.swing.JComponent; import javax.swing.JLabel; @@ -12,13 +12,13 @@ import org.jabref.model.database.BibDatabase; import org.jabref.model.groups.GroupTreeNode; -class GroupChange extends Change { +class GroupChangeViewModel extends ChangeViewModel { private final GroupTreeNode changedGroups; private final GroupTreeNode tmpGroupRoot; - public GroupChange(GroupTreeNode changedGroups, GroupTreeNode tmpGroupRoot) { + public GroupChangeViewModel(GroupTreeNode changedGroups, GroupTreeNode tmpGroupRoot) { super(changedGroups == null ? Localization.lang("Removed all groups") : Localization .lang("Modified groups tree")); this.changedGroups = changedGroups; diff --git a/src/main/java/org/jabref/collab/InfoPane.java b/src/main/java/org/jabref/gui/collab/InfoPane.java similarity index 94% rename from src/main/java/org/jabref/collab/InfoPane.java rename to src/main/java/org/jabref/gui/collab/InfoPane.java index 9d7e23689eb..cec4658a5de 100644 --- a/src/main/java/org/jabref/collab/InfoPane.java +++ b/src/main/java/org/jabref/gui/collab/InfoPane.java @@ -1,4 +1,4 @@ -package org.jabref.collab; +package org.jabref.gui.collab; import java.awt.Graphics; import java.awt.Graphics2D; diff --git a/src/main/java/org/jabref/collab/MetaDataChange.java b/src/main/java/org/jabref/gui/collab/MetaDataChangeViewModel.java similarity index 89% rename from src/main/java/org/jabref/collab/MetaDataChange.java rename to src/main/java/org/jabref/gui/collab/MetaDataChangeViewModel.java index 0a6fd0784eb..5ac134ac7e9 100644 --- a/src/main/java/org/jabref/collab/MetaDataChange.java +++ b/src/main/java/org/jabref/gui/collab/MetaDataChangeViewModel.java @@ -1,4 +1,4 @@ -package org.jabref.collab; +package org.jabref.gui.collab; import javax.swing.JComponent; import javax.swing.JScrollPane; @@ -12,14 +12,14 @@ /** * */ -class MetaDataChange extends Change { +class MetaDataChangeViewModel extends ChangeViewModel { private final InfoPane infoPane = new InfoPane(); private final JScrollPane sp = new JScrollPane(infoPane); private final MetaData originalMetaData; private final MetaData newMetaData; - public MetaDataChange(MetaData originalMetaData, MetaData newMetaData) { + public MetaDataChangeViewModel(MetaData originalMetaData, MetaData newMetaData) { super(Localization.lang("Metadata change")); this.originalMetaData = originalMetaData; this.newMetaData = newMetaData; diff --git a/src/main/java/org/jabref/collab/PreambleChange.java b/src/main/java/org/jabref/gui/collab/PreambleChangeViewModel.java similarity index 91% rename from src/main/java/org/jabref/collab/PreambleChange.java rename to src/main/java/org/jabref/gui/collab/PreambleChangeViewModel.java index 3245ed24d74..e207c451c3e 100644 --- a/src/main/java/org/jabref/collab/PreambleChange.java +++ b/src/main/java/org/jabref/gui/collab/PreambleChangeViewModel.java @@ -1,4 +1,4 @@ -package org.jabref.collab; +package org.jabref.gui.collab; import javax.swing.JComponent; import javax.swing.JScrollPane; @@ -9,7 +9,7 @@ import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabase; -class PreambleChange extends Change { +class PreambleChangeViewModel extends ChangeViewModel { private final String mem; private final String disk; @@ -17,7 +17,7 @@ class PreambleChange extends Change { private final JScrollPane sp = new JScrollPane(tp); - public PreambleChange(String mem, String disk) { + public PreambleChangeViewModel(String mem, String disk) { super(Localization.lang("Changed preamble")); this.disk = disk; this.mem = mem; diff --git a/src/main/java/org/jabref/collab/StringAddChange.java b/src/main/java/org/jabref/gui/collab/StringAddChangeViewModel.java similarity index 92% rename from src/main/java/org/jabref/collab/StringAddChange.java rename to src/main/java/org/jabref/gui/collab/StringAddChangeViewModel.java index b2829830ca1..ba7ff79648a 100644 --- a/src/main/java/org/jabref/collab/StringAddChange.java +++ b/src/main/java/org/jabref/gui/collab/StringAddChangeViewModel.java @@ -1,4 +1,4 @@ -package org.jabref.collab; +package org.jabref.gui.collab; import javax.swing.JComponent; import javax.swing.JScrollPane; @@ -14,9 +14,9 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -class StringAddChange extends Change { +class StringAddChangeViewModel extends ChangeViewModel { - private static final Log LOGGER = LogFactory.getLog(StringAddChange.class); + private static final Log LOGGER = LogFactory.getLog(StringAddChangeViewModel.class); private final BibtexString string; private final InfoPane tp = new InfoPane(); @@ -24,7 +24,7 @@ class StringAddChange extends Change { private final JScrollPane sp = new JScrollPane(tp); - public StringAddChange(BibtexString string) { + public StringAddChangeViewModel(BibtexString string) { super(Localization.lang("Added string") + ": '" + string.getName() + '\''); this.string = string; tp.setText("

" + Localization.lang("Added string") + "

" + diff --git a/src/main/java/org/jabref/collab/StringChange.java b/src/main/java/org/jabref/gui/collab/StringChangeViewModel.java similarity index 93% rename from src/main/java/org/jabref/collab/StringChange.java rename to src/main/java/org/jabref/gui/collab/StringChangeViewModel.java index c93847d716f..8bf508382d5 100644 --- a/src/main/java/org/jabref/collab/StringChange.java +++ b/src/main/java/org/jabref/gui/collab/StringChangeViewModel.java @@ -1,4 +1,4 @@ -package org.jabref.collab; +package org.jabref.gui.collab; import javax.swing.JComponent; import javax.swing.JScrollPane; @@ -15,9 +15,9 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -class StringChange extends Change { +class StringChangeViewModel extends ChangeViewModel { - private static final Log LOGGER = LogFactory.getLog(StringChange.class); + private static final Log LOGGER = LogFactory.getLog(StringChangeViewModel.class); private final BibtexString string; private final String mem; private final String disk; @@ -29,7 +29,7 @@ class StringChange extends Change { private final BibtexString tmpString; - public StringChange(BibtexString string, BibtexString tmpString, String label, String mem, String disk) { + public StringChangeViewModel(BibtexString string, BibtexString tmpString, String label, String mem, String disk) { super(Localization.lang("Modified string") + ": '" + label + '\''); this.tmpString = tmpString; this.string = string; diff --git a/src/main/java/org/jabref/collab/StringNameChange.java b/src/main/java/org/jabref/gui/collab/StringNameChangeViewModel.java similarity index 89% rename from src/main/java/org/jabref/collab/StringNameChange.java rename to src/main/java/org/jabref/gui/collab/StringNameChangeViewModel.java index b53e7e63098..cafba33fecf 100644 --- a/src/main/java/org/jabref/collab/StringNameChange.java +++ b/src/main/java/org/jabref/gui/collab/StringNameChangeViewModel.java @@ -1,4 +1,4 @@ -package org.jabref.collab; +package org.jabref.gui.collab; import javax.swing.JComponent; import javax.swing.JLabel; @@ -15,9 +15,9 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -class StringNameChange extends Change { +class StringNameChangeViewModel extends ChangeViewModel { - private static final Log LOGGER = LogFactory.getLog(StringNameChange.class); + private static final Log LOGGER = LogFactory.getLog(StringNameChangeViewModel.class); private final BibtexString string; private final String mem; private final String disk; @@ -26,8 +26,8 @@ class StringNameChange extends Change { private final BibtexString tmpString; - public StringNameChange(BibtexString string, BibtexString tmpString, - String mem, String tmp, String disk, String content) { + public StringNameChangeViewModel(BibtexString string, BibtexString tmpString, + String mem, String tmp, String disk, String content) { super(Localization.lang("Renamed string") + ": '" + tmp + '\''); this.tmpString = tmpString; this.string = string; diff --git a/src/main/java/org/jabref/collab/StringRemoveChange.java b/src/main/java/org/jabref/gui/collab/StringRemoveChangeViewModel.java similarity index 89% rename from src/main/java/org/jabref/collab/StringRemoveChange.java rename to src/main/java/org/jabref/gui/collab/StringRemoveChangeViewModel.java index 455704722ea..4e1bb84b6ac 100644 --- a/src/main/java/org/jabref/collab/StringRemoveChange.java +++ b/src/main/java/org/jabref/gui/collab/StringRemoveChangeViewModel.java @@ -1,4 +1,4 @@ -package org.jabref.collab; +package org.jabref.gui.collab; import javax.swing.JComponent; import javax.swing.JScrollPane; @@ -13,8 +13,8 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -class StringRemoveChange extends Change { - private static final Log LOGGER = LogFactory.getLog(StringRemoveChange.class); +class StringRemoveChangeViewModel extends ChangeViewModel { + private static final Log LOGGER = LogFactory.getLog(StringRemoveChangeViewModel.class); private final BibtexString string; private final BibtexString inMem; @@ -24,7 +24,7 @@ class StringRemoveChange extends Change { private final BibtexString tmpString; - public StringRemoveChange(BibtexString string, BibtexString tmpString, BibtexString inMem) { + public StringRemoveChangeViewModel(BibtexString string, BibtexString tmpString, BibtexString inMem) { super(Localization.lang("Removed string") + ": '" + string.getName() + '\''); this.tmpString = tmpString; this.string = string; diff --git a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java index f54bb076a99..25007ea94a2 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java @@ -12,13 +12,13 @@ import org.jabref.Globals; import org.jabref.JabRefExecutorService; -import org.jabref.collab.ChangeScanner; -import org.jabref.collab.FileUpdatePanel; import org.jabref.gui.BasePanel; import org.jabref.gui.DialogService; import org.jabref.gui.FXDialogService; import org.jabref.gui.JabRefFrame; import org.jabref.gui.autosaveandbackup.AutosaveUIManager; +import org.jabref.gui.collab.ChangeScanner; +import org.jabref.gui.collab.FileUpdatePanel; import org.jabref.gui.util.DefaultTaskExecutor; import org.jabref.gui.util.FileDialogConfiguration; import org.jabref.gui.worker.AbstractWorker; From 1e54ebf5bb5aaeb3deb0f9d4d72b17fc1865ef21 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Mon, 23 Oct 2017 23:39:27 +0800 Subject: [PATCH 03/22] Remove unnecessary EntrySorter --- .../org/jabref/gui/collab/ChangeScanner.java | 171 +++++++++--------- .../jabref/model/database/BibDatabase.java | 10 +- .../jabref/model/database/EntrySorter.java | 27 --- .../model/database/BibDatabaseTest.java | 11 ++ .../model/database/EntrySorterTest.java | 41 ----- 5 files changed, 102 insertions(+), 158 deletions(-) delete mode 100644 src/main/java/org/jabref/model/database/EntrySorter.java delete mode 100644 src/test/java/org/jabref/model/database/EntrySorterTest.java diff --git a/src/main/java/org/jabref/gui/collab/ChangeScanner.java b/src/main/java/org/jabref/gui/collab/ChangeScanner.java index bf84b9a6e0c..14c183ce9e8 100644 --- a/src/main/java/org/jabref/gui/collab/ChangeScanner.java +++ b/src/main/java/org/jabref/gui/collab/ChangeScanner.java @@ -5,6 +5,7 @@ import java.nio.file.Path; import java.util.HashSet; import java.util.Iterator; +import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -32,7 +33,6 @@ import org.jabref.model.Defaults; import org.jabref.model.database.BibDatabase; import org.jabref.model.database.BibDatabaseContext; -import org.jabref.model.database.EntrySorter; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.BibtexString; import org.jabref.model.entry.FieldName; @@ -77,47 +77,29 @@ public ChangeScanner(JabRefFrame frame, BasePanel bp, File file, Path tempFile) this.tempFile = tempFile; } - @Override - public void run() { - try { - - // Parse the temporary file. - ImportFormatPreferences importFormatPreferences = Globals.prefs.getImportFormatPreferences(); - ParserResult result = OpenDatabase.loadDatabase(tempFile.toFile(), importFormatPreferences); - databaseInTemp = result.getDatabase(); - metadataInTemp = result.getMetaData(); - - // Parse the modified file. - result = OpenDatabase.loadDatabase(file, importFormatPreferences); - BibDatabase databaseOnDisk = result.getDatabase(); - MetaData metadataOnDisk = result.getMetaData(); - - // Sort both databases according to a common sort key. - EntryComparator comparator = new EntryComparator(false, true, SORT_BY[2]); - comparator = new EntryComparator(false, true, SORT_BY[1], comparator); - comparator = new EntryComparator(false, true, SORT_BY[0], comparator); - EntrySorter sorterInTemp = databaseInTemp.getSorter(comparator); - comparator = new EntryComparator(false, true, SORT_BY[2]); - comparator = new EntryComparator(false, true, SORT_BY[1], comparator); - comparator = new EntryComparator(false, true, SORT_BY[0], comparator); - EntrySorter sorterOnDisk = databaseOnDisk.getSorter(comparator); - comparator = new EntryComparator(false, true, SORT_BY[2]); - comparator = new EntryComparator(false, true, SORT_BY[1], comparator); - comparator = new EntryComparator(false, true, SORT_BY[0], comparator); - EntrySorter sorterInMem = databaseInMemory.getSorter(comparator); - - // Start looking at changes. - scanMetaData(metadataInMemory, metadataInTemp, metadataOnDisk); - scanPreamble(databaseInMemory, databaseInTemp, databaseOnDisk); - scanStrings(databaseInMemory, databaseInTemp, databaseOnDisk); - - scanEntries(sorterInMem, sorterInTemp, sorterOnDisk); - - scanGroups(metadataInTemp, metadataOnDisk); - - } catch (IOException ex) { - LOGGER.warn("Problem running", ex); + /** + * Finds the entry in neu best fitting the specified entry in old. If no entries get a score above zero, an entry is + * still returned. + * + * @param oldSorter EntrySorter + * @param newSorter EntrySorter + * @param index int + * @return BibEntry + */ + private static BibEntry bestFit(List oldSorter, List newSorter, int index) { + double comp = -1; + int found = 0; + for (int i = 0; i < newSorter.size(); i++) { + double res = DuplicateCheck.compareEntriesStrictly(oldSorter.get(index), newSorter.get(i)); + if (res > comp) { + comp = res; + found = i; + } + if (comp > 1) { + break; + } } + return newSorter.get(found); } public boolean changesFound() { @@ -173,7 +155,49 @@ private void scanMetaData(MetaData inMemory, MetaData onTmp, MetaData onDisk) { } } - private void scanEntries(EntrySorter memorySorter, EntrySorter tmpSorter, EntrySorter diskSorter) { + @Override + public void run() { + try { + + // Parse the temporary file. + ImportFormatPreferences importFormatPreferences = Globals.prefs.getImportFormatPreferences(); + ParserResult result = OpenDatabase.loadDatabase(tempFile.toFile(), importFormatPreferences); + databaseInTemp = result.getDatabase(); + metadataInTemp = result.getMetaData(); + + // Parse the modified file. + result = OpenDatabase.loadDatabase(file, importFormatPreferences); + BibDatabase databaseOnDisk = result.getDatabase(); + MetaData metadataOnDisk = result.getMetaData(); + + // Sort both databases according to a common sort key. + EntryComparator comparator = new EntryComparator(false, true, SORT_BY[2]); + comparator = new EntryComparator(false, true, SORT_BY[1], comparator); + comparator = new EntryComparator(false, true, SORT_BY[0], comparator); + List sorterInTemp = databaseInTemp.getEntriesSorted(comparator); + comparator = new EntryComparator(false, true, SORT_BY[2]); + comparator = new EntryComparator(false, true, SORT_BY[1], comparator); + comparator = new EntryComparator(false, true, SORT_BY[0], comparator); + List sorterOnDisk = databaseOnDisk.getEntriesSorted(comparator); + comparator = new EntryComparator(false, true, SORT_BY[2]); + comparator = new EntryComparator(false, true, SORT_BY[1], comparator); + comparator = new EntryComparator(false, true, SORT_BY[0], comparator); + List sorterInMem = databaseInMemory.getEntriesSorted(comparator); + + // Start looking at changes. + scanMetaData(metadataInMemory, metadataInTemp, metadataOnDisk); + scanPreamble(databaseInMemory, databaseInTemp, databaseOnDisk); + scanStrings(databaseInMemory, databaseInTemp, databaseOnDisk); + + scanEntries(sorterInMem, sorterInTemp, sorterOnDisk); + + scanGroups(metadataInTemp, metadataOnDisk); + } catch (IOException ex) { + LOGGER.warn("Problem running", ex); + } + } + + private void scanEntries(List memorySorter, List tmpSorter, List diskSorter) { // Create pointers that are incremented as the entries of each base are used in // successive order from the beginning. Entries "further down" in the "disk" base @@ -183,21 +207,21 @@ private void scanEntries(EntrySorter memorySorter, EntrySorter tmpSorter, EntryS // Create a HashSet where we can put references to entry numbers in the "disk" // database that we have matched. This is to avoid matching them twice. - Set used = new HashSet<>(diskSorter.getEntryCount()); - Set notMatched = new HashSet<>(tmpSorter.getEntryCount()); + Set used = new HashSet<>(diskSorter.size()); + Set notMatched = new HashSet<>(tmpSorter.size()); // Loop through the entries of the "tmp" database, looking for exact matches in the "disk" one. // We must finish scanning for exact matches before looking for near matches, to avoid an exact // match being "stolen" from another entry. mainLoop: - for (piv1 = 0; piv1 < tmpSorter.getEntryCount(); piv1++) { + for (piv1 = 0; piv1 < tmpSorter.size(); piv1++) { // First check if the similarly placed entry in the other base matches exactly. double comp = -1; // (if there are not any entries left in the "disk" database, comp will stay at -1, // and this entry will be marked as nonmatched). - if (!used.contains(String.valueOf(piv2)) && (piv2 < diskSorter.getEntryCount())) { - comp = DuplicateCheck.compareEntriesStrictly(tmpSorter.getEntryAt(piv1), diskSorter.getEntryAt(piv2)); + if (!used.contains(String.valueOf(piv2)) && (piv2 < diskSorter.size())) { + comp = DuplicateCheck.compareEntriesStrictly(tmpSorter.get(piv1), diskSorter.get(piv2)); } if (comp > 1) { used.add(String.valueOf(piv2)); @@ -206,12 +230,12 @@ private void scanEntries(EntrySorter memorySorter, EntrySorter tmpSorter, EntryS } // No? Then check if another entry matches exactly. - if (piv2 < (diskSorter.getEntryCount() - 1)) { - for (int i = piv2 + 1; i < diskSorter.getEntryCount(); i++) { + if (piv2 < (diskSorter.size() - 1)) { + for (int i = piv2 + 1; i < diskSorter.size(); i++) { if (used.contains(String.valueOf(i))) { comp = -1; } else { - comp = DuplicateCheck.compareEntriesStrictly(tmpSorter.getEntryAt(piv1), diskSorter.getEntryAt(i)); + comp = DuplicateCheck.compareEntriesStrictly(tmpSorter.get(piv1), diskSorter.get(i)); } if (comp > 1) { @@ -239,12 +263,12 @@ private void scanEntries(EntrySorter memorySorter, EntrySorter tmpSorter, EntryS double bestMatch = 0; double comp; - if (piv2 < (diskSorter.getEntryCount() - 1)) { - for (int i = piv2; i < diskSorter.getEntryCount(); i++) { + if (piv2 < (diskSorter.size() - 1)) { + for (int i = piv2; i < diskSorter.size(); i++) { if (used.contains(String.valueOf(i))) { comp = -1; } else { - comp = DuplicateCheck.compareEntriesStrictly(tmpSorter.getEntryAt(piv1), diskSorter.getEntryAt(i)); + comp = DuplicateCheck.compareEntriesStrictly(tmpSorter.get(piv1), diskSorter.get(i)); } if (comp > bestMatch) { @@ -258,11 +282,11 @@ private void scanEntries(EntrySorter memorySorter, EntrySorter tmpSorter, EntryS used.add(String.valueOf(bestMatchI)); it.remove(); - changes.add(new EntryChangeViewModel(bestFit(tmpSorter, memorySorter, piv1), tmpSorter.getEntryAt(piv1), - diskSorter.getEntryAt(bestMatchI))); + changes.add(new EntryChangeViewModel(bestFit(tmpSorter, memorySorter, piv1), tmpSorter.get(piv1), + diskSorter.get(bestMatchI))); } else { changes.add( - new EntryDeleteChangeViewModel(bestFit(tmpSorter, memorySorter, piv1), tmpSorter.getEntryAt(piv1))); + new EntryDeleteChangeViewModel(bestFit(tmpSorter, memorySorter, piv1), tmpSorter.get(piv1))); } } @@ -271,51 +295,26 @@ private void scanEntries(EntrySorter memorySorter, EntrySorter tmpSorter, EntryS // Finally, look if there are still untouched entries in the disk database. These // may have been added. - if (used.size() < diskSorter.getEntryCount()) { - for (int i = 0; i < diskSorter.getEntryCount(); i++) { + if (used.size() < diskSorter.size()) { + for (int i = 0; i < diskSorter.size(); i++) { if (!used.contains(String.valueOf(i))) { // See if there is an identical dupe in the mem database: boolean hasAlready = false; - for (int j = 0; j < memorySorter.getEntryCount(); j++) { - if (DuplicateCheck.compareEntriesStrictly(memorySorter.getEntryAt(j), diskSorter.getEntryAt(i)) >= 1) { + for (int j = 0; j < memorySorter.size(); j++) { + if (DuplicateCheck.compareEntriesStrictly(memorySorter.get(j), diskSorter.get(i)) >= 1) { hasAlready = true; break; } } if (!hasAlready) { - changes.add(new EntryAddChangeViewModel(diskSorter.getEntryAt(i))); + changes.add(new EntryAddChangeViewModel(diskSorter.get(i))); } } } } } - /** - * Finds the entry in neu best fitting the specified entry in old. If no entries get a score - * above zero, an entry is still returned. - * - * @param oldSorter EntrySorter - * @param newSorter EntrySorter - * @param index int - * @return BibEntry - */ - private static BibEntry bestFit(EntrySorter oldSorter, EntrySorter newSorter, int index) { - double comp = -1; - int found = 0; - for (int i = 0; i < newSorter.getEntryCount(); i++) { - double res = DuplicateCheck.compareEntriesStrictly(oldSorter.getEntryAt(index), newSorter.getEntryAt(i)); - if (res > comp) { - comp = res; - found = i; - } - if (comp > 1) { - break; - } - } - return newSorter.getEntryAt(found); - } - private void scanPreamble(BibDatabase inMemory, BibDatabase onTmp, BibDatabase onDisk) { String mem = inMemory.getPreamble().orElse(null); Optional tmp = onTmp.getPreamble(); diff --git a/src/main/java/org/jabref/model/database/BibDatabase.java b/src/main/java/org/jabref/model/database/BibDatabase.java index 5223c86846a..6a067d783de 100644 --- a/src/main/java/org/jabref/model/database/BibDatabase.java +++ b/src/main/java/org/jabref/model/database/BibDatabase.java @@ -99,11 +99,13 @@ public boolean hasEntries() { } /** - * Returns an EntrySorter with the sorted entries from this base, - * sorted by the given Comparator. + * Returns the list of entries sorted by the given comparator. */ - public synchronized EntrySorter getSorter(Comparator comp) { - return new EntrySorter(new ArrayList<>(getEntries()), comp); + public synchronized List getEntriesSorted(Comparator comparator) { + List entriesSorted = new ArrayList<>(entries); + entriesSorted.sort(comparator); + + return entriesSorted; } /** diff --git a/src/main/java/org/jabref/model/database/EntrySorter.java b/src/main/java/org/jabref/model/database/EntrySorter.java deleted file mode 100644 index f2041d5519c..00000000000 --- a/src/main/java/org/jabref/model/database/EntrySorter.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.jabref.model.database; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -import org.jabref.model.entry.BibEntry; - -public class EntrySorter { - - private final List entries; - - public EntrySorter(List entries, Comparator comparator) { - this.entries = new ArrayList<>(entries); - Collections.sort(this.entries, comparator); - } - - public BibEntry getEntryAt(int pos) { - return entries.get(pos); - } - - public int getEntryCount() { - return entries.size(); - } - -} diff --git a/src/test/java/org/jabref/model/database/BibDatabaseTest.java b/src/test/java/org/jabref/model/database/BibDatabaseTest.java index a3630366a56..a6b97c24ccb 100644 --- a/src/test/java/org/jabref/model/database/BibDatabaseTest.java +++ b/src/test/java/org/jabref/model/database/BibDatabaseTest.java @@ -4,6 +4,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Optional; @@ -305,6 +306,16 @@ public void getUsedStringsNoString() { assertEquals(Collections.emptyList(), usedStrings); } + @Test + public void getEntriesSortedWithTwoEntries() { + BibEntry entryB = new BibEntry("article"); + entryB.setId("2"); + BibEntry entryA = new BibEntry("article"); + entryB.setId("1"); + database.insertEntries(entryB, entryA); + assertEquals(Arrays.asList(entryA, entryB), database.getEntriesSorted(Comparator.comparing(BibEntry::getId))); + } + @Test public void preambleIsEmptyIfNotSet() { assertEquals(Optional.empty(), database.getPreamble()); diff --git a/src/test/java/org/jabref/model/database/EntrySorterTest.java b/src/test/java/org/jabref/model/database/EntrySorterTest.java deleted file mode 100644 index ce3db504044..00000000000 --- a/src/test/java/org/jabref/model/database/EntrySorterTest.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.jabref.model.database; - -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; - -import org.jabref.model.entry.BibEntry; - -import org.junit.Test; - -import static org.junit.Assert.assertEquals; - -public class EntrySorterTest { - - @Test - public void testEmptyEntrySorter() throws Exception { - EntrySorter es = new EntrySorter(Collections.emptyList(), Comparator.comparing(BibEntry::getId)); - assertEquals(0, es.getEntryCount()); - } - - @Test - public void testEntrySorterWithOneElement() throws Exception { - BibEntry entryA = new BibEntry("article"); - EntrySorter es = new EntrySorter(Collections.singletonList(entryA), Comparator.comparing(BibEntry::getId)); - assertEquals(1, es.getEntryCount()); - assertEquals(entryA, es.getEntryAt(0)); - } - - @Test - public void testEntrySorterWithTwoElements() throws Exception { - BibEntry entryB = new BibEntry("article"); - entryB.setId("2"); - BibEntry entryA = new BibEntry("article"); - entryB.setId("1"); - EntrySorter es = new EntrySorter(Arrays.asList(entryB, entryA), Comparator.comparing(BibEntry::getId)); - assertEquals(2, es.getEntryCount()); - assertEquals(entryA, es.getEntryAt(0)); - assertEquals(entryB, es.getEntryAt(1)); - } - -} From 68d119f05cd5e4faaa141ec40014bce45e48e35c Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Thu, 26 Oct 2017 00:15:25 +0800 Subject: [PATCH 04/22] Extract difference finder from gui to logic --- .../org/jabref/gui/collab/ChangeScanner.java | 381 +++--------------- .../gui/collab/GroupChangeViewModel.java | 9 +- .../gui/collab/MetaDataChangeViewModel.java | 5 +- .../gui/collab/PreambleChangeViewModel.java | 10 +- .../gui/collab/StringChangeViewModel.java | 9 +- .../gui/collab/StringNameChangeViewModel.java | 7 +- .../collab/StringRemoveChangeViewModel.java | 8 +- .../bibtex/comparator/BibDatabaseDiff.java | 144 +++++++ .../logic/bibtex/comparator/BibEntryDiff.java | 21 + .../bibtex/comparator/BibStringDiff.java | 89 ++++ .../logic/bibtex/comparator/GroupDiff.java | 39 ++ .../logic/bibtex/comparator/MetaDataDiff.java | 32 ++ .../logic/bibtex/comparator/PreambleDiff.java | 28 ++ .../jabref/model/database/BibDatabase.java | 12 + 14 files changed, 434 insertions(+), 360 deletions(-) create mode 100644 src/main/java/org/jabref/logic/bibtex/comparator/BibDatabaseDiff.java create mode 100644 src/main/java/org/jabref/logic/bibtex/comparator/BibEntryDiff.java create mode 100644 src/main/java/org/jabref/logic/bibtex/comparator/BibStringDiff.java create mode 100644 src/main/java/org/jabref/logic/bibtex/comparator/GroupDiff.java create mode 100644 src/main/java/org/jabref/logic/bibtex/comparator/MetaDataDiff.java create mode 100644 src/main/java/org/jabref/logic/bibtex/comparator/PreambleDiff.java diff --git a/src/main/java/org/jabref/gui/collab/ChangeScanner.java b/src/main/java/org/jabref/gui/collab/ChangeScanner.java index 14c183ce9e8..a35ca71bad0 100644 --- a/src/main/java/org/jabref/gui/collab/ChangeScanner.java +++ b/src/main/java/org/jabref/gui/collab/ChangeScanner.java @@ -3,12 +3,9 @@ import java.io.File; import java.io.IOException; import java.nio.file.Path; -import java.util.HashSet; -import java.util.Iterator; +import java.util.Comparator; import java.util.List; -import java.util.Objects; import java.util.Optional; -import java.util.Set; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; @@ -19,7 +16,9 @@ import org.jabref.gui.BasePanel; import org.jabref.gui.JabRefFrame; import org.jabref.logic.bibtex.DuplicateCheck; -import org.jabref.logic.bibtex.comparator.EntryComparator; +import org.jabref.logic.bibtex.comparator.BibDatabaseDiff; +import org.jabref.logic.bibtex.comparator.BibEntryDiff; +import org.jabref.logic.bibtex.comparator.BibStringDiff; import org.jabref.logic.exporter.BibDatabaseWriter; import org.jabref.logic.exporter.BibtexDatabaseWriter; import org.jabref.logic.exporter.FileSaveSession; @@ -30,13 +29,9 @@ import org.jabref.logic.importer.OpenDatabase; import org.jabref.logic.importer.ParserResult; import org.jabref.logic.l10n.Localization; -import org.jabref.model.Defaults; -import org.jabref.model.database.BibDatabase; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.BibtexString; -import org.jabref.model.entry.FieldName; -import org.jabref.model.groups.GroupTreeNode; import org.jabref.model.metadata.MetaData; import org.apache.commons.logging.Log; @@ -45,19 +40,14 @@ public class ChangeScanner implements Runnable { private static final Log LOGGER = LogFactory.getLog(ChangeScanner.class); - private static final String[] SORT_BY = new String[] {FieldName.YEAR, FieldName.AUTHOR, FieldName.TITLE}; - - private static final double MATCH_THRESHOLD = 0.4; private final File file; private final Path tempFile; - private final BibDatabase databaseInMemory; + private final BibDatabaseContext databaseInMemory; private final MetaData metadataInMemory; private final BasePanel panel; private final JabRefFrame frame; - private BibDatabase databaseInTemp; - - private MetaData metadataInTemp; + private BibDatabaseContext databaseInTemp; /** * We create an ArrayList to hold the changes we find. These will be added in the form @@ -71,45 +61,30 @@ public class ChangeScanner implements Runnable { public ChangeScanner(JabRefFrame frame, BasePanel bp, File file, Path tempFile) { this.panel = bp; this.frame = frame; - this.databaseInMemory = bp.getDatabase(); + this.databaseInMemory = bp.getDatabaseContext(); this.metadataInMemory = bp.getBibDatabaseContext().getMetaData(); this.file = file; this.tempFile = tempFile; } - /** - * Finds the entry in neu best fitting the specified entry in old. If no entries get a score above zero, an entry is - * still returned. - * - * @param oldSorter EntrySorter - * @param newSorter EntrySorter - * @param index int - * @return BibEntry - */ - private static BibEntry bestFit(List oldSorter, List newSorter, int index) { - double comp = -1; - int found = 0; - for (int i = 0; i < newSorter.size(); i++) { - double res = DuplicateCheck.compareEntriesStrictly(oldSorter.get(index), newSorter.get(i)); - if (res > comp) { - comp = res; - found = i; - } - if (comp > 1) { - break; - } - } - return newSorter.get(found); - } - public boolean changesFound() { return changes.getChildCount() > 0; } + /** + * Finds the entry in the list best fitting the specified entry. Even if no entries get a score above zero, an entry + * is still returned. + */ + private static BibEntry bestFit(BibEntry targetEntry, List entries) { + return entries.stream() + .max(Comparator.comparingDouble(candidate -> DuplicateCheck.compareEntriesStrictly(targetEntry, candidate))) + .orElse(null); + } + public void displayResult(final DisplayResultCallback fup) { if (changes.getChildCount() > 0) { SwingUtilities.invokeLater(() -> { - ChangeDisplayDialog changeDialog = new ChangeDisplayDialog(frame, panel, databaseInTemp, changes); + ChangeDisplayDialog changeDialog = new ChangeDisplayDialog(frame, panel, databaseInTemp.getDatabase(), changes); changeDialog.setLocationRelativeTo(frame); changeDialog.setVisible(true); fup.scanResultsResolved(changeDialog.isOkPressed()); @@ -133,9 +108,8 @@ private void storeTempDatabase() { .withEncoding(panel.getBibDatabaseContext().getMetaData().getEncoding() .orElse(Globals.prefs.getDefaultEncoding())); - Defaults defaults = new Defaults(Globals.prefs.getDefaultBibDatabaseMode()); BibDatabaseWriter databaseWriter = new BibtexDatabaseWriter<>(FileSaveSession::new); - SaveSession ss = databaseWriter.saveDatabase(new BibDatabaseContext(databaseInTemp, metadataInTemp, defaults), prefs); + SaveSession ss = databaseWriter.saveDatabase(databaseInTemp, prefs); ss.commit(tempFile); } catch (SaveException ex) { LOGGER.warn("Problem updating tmp file after accepting external changes", ex); @@ -143,18 +117,6 @@ private void storeTempDatabase() { }); } - private void scanMetaData(MetaData inMemory, MetaData onTmp, MetaData onDisk) { - if (onTmp.isEmpty()) { - if (!onDisk.isEmpty() || !onTmp.equals(onDisk)) { - changes.add(new MetaDataChangeViewModel(inMemory, onDisk)); - } - } else { - if (!inMemory.equals(onDisk)) { - changes.add(new MetaDataChangeViewModel(inMemory, onDisk)); - } - } - } - @Override public void run() { try { @@ -162,306 +124,55 @@ public void run() { // Parse the temporary file. ImportFormatPreferences importFormatPreferences = Globals.prefs.getImportFormatPreferences(); ParserResult result = OpenDatabase.loadDatabase(tempFile.toFile(), importFormatPreferences); - databaseInTemp = result.getDatabase(); - metadataInTemp = result.getMetaData(); + databaseInTemp = result.getDatabaseContext(); // Parse the modified file. result = OpenDatabase.loadDatabase(file, importFormatPreferences); - BibDatabase databaseOnDisk = result.getDatabase(); - MetaData metadataOnDisk = result.getMetaData(); - - // Sort both databases according to a common sort key. - EntryComparator comparator = new EntryComparator(false, true, SORT_BY[2]); - comparator = new EntryComparator(false, true, SORT_BY[1], comparator); - comparator = new EntryComparator(false, true, SORT_BY[0], comparator); - List sorterInTemp = databaseInTemp.getEntriesSorted(comparator); - comparator = new EntryComparator(false, true, SORT_BY[2]); - comparator = new EntryComparator(false, true, SORT_BY[1], comparator); - comparator = new EntryComparator(false, true, SORT_BY[0], comparator); - List sorterOnDisk = databaseOnDisk.getEntriesSorted(comparator); - comparator = new EntryComparator(false, true, SORT_BY[2]); - comparator = new EntryComparator(false, true, SORT_BY[1], comparator); - comparator = new EntryComparator(false, true, SORT_BY[0], comparator); - List sorterInMem = databaseInMemory.getEntriesSorted(comparator); + BibDatabaseContext databaseOnDisk = result.getDatabaseContext(); // Start looking at changes. - scanMetaData(metadataInMemory, metadataInTemp, metadataOnDisk); - scanPreamble(databaseInMemory, databaseInTemp, databaseOnDisk); - scanStrings(databaseInMemory, databaseInTemp, databaseOnDisk); - - scanEntries(sorterInMem, sorterInTemp, sorterOnDisk); - - scanGroups(metadataInTemp, metadataOnDisk); + BibDatabaseDiff differences = BibDatabaseDiff.compare(databaseInTemp, databaseOnDisk); + differences.getMetaDataDifferences().ifPresent(diff -> { + changes.add(new MetaDataChangeViewModel(metadataInMemory, diff)); + diff.getGroupDifferences().ifPresent(groupDiff -> changes.add(new GroupChangeViewModel(groupDiff))); + }); + differences.getPreambleDifferences().ifPresent(diff -> changes.add(new PreambleChangeViewModel(databaseInMemory.getDatabase().getPreamble().orElse(""), diff))); + differences.getBibStringDifferences().forEach(diff -> changes.add(createBibStringDiff(diff))); + differences.getEntryDifferences().forEach(diff -> changes.add(createBibEntryDiff(diff))); } catch (IOException ex) { LOGGER.warn("Problem running", ex); } } - private void scanEntries(List memorySorter, List tmpSorter, List diskSorter) { - - // Create pointers that are incremented as the entries of each base are used in - // successive order from the beginning. Entries "further down" in the "disk" base - // can also be matched. - int piv1; - int piv2 = 0; - - // Create a HashSet where we can put references to entry numbers in the "disk" - // database that we have matched. This is to avoid matching them twice. - Set used = new HashSet<>(diskSorter.size()); - Set notMatched = new HashSet<>(tmpSorter.size()); - - // Loop through the entries of the "tmp" database, looking for exact matches in the "disk" one. - // We must finish scanning for exact matches before looking for near matches, to avoid an exact - // match being "stolen" from another entry. - mainLoop: - for (piv1 = 0; piv1 < tmpSorter.size(); piv1++) { - - // First check if the similarly placed entry in the other base matches exactly. - double comp = -1; - // (if there are not any entries left in the "disk" database, comp will stay at -1, - // and this entry will be marked as nonmatched). - if (!used.contains(String.valueOf(piv2)) && (piv2 < diskSorter.size())) { - comp = DuplicateCheck.compareEntriesStrictly(tmpSorter.get(piv1), diskSorter.get(piv2)); - } - if (comp > 1) { - used.add(String.valueOf(piv2)); - piv2++; - continue; - } - - // No? Then check if another entry matches exactly. - if (piv2 < (diskSorter.size() - 1)) { - for (int i = piv2 + 1; i < diskSorter.size(); i++) { - if (used.contains(String.valueOf(i))) { - comp = -1; - } else { - comp = DuplicateCheck.compareEntriesStrictly(tmpSorter.get(piv1), diskSorter.get(i)); - } - - if (comp > 1) { - used.add(String.valueOf(i)); - continue mainLoop; - } - } - } - - // No? Add this entry to the list of nonmatched entries. - notMatched.add(piv1); + private ChangeViewModel createBibStringDiff(BibStringDiff diff) { + if (diff.getOriginalString() == null) { + return new StringAddChangeViewModel(diff.getNewString()); } - // Now we've found all exact matches, look through the remaining entries, looking - // for close matches. - if (!notMatched.isEmpty()) { - - for (Iterator it = notMatched.iterator(); it.hasNext(); ) { - - piv1 = it.next(); - - // These two variables will keep track of which entry most closely matches the - // one we're looking at, in case none matches completely. - int bestMatchI = -1; - double bestMatch = 0; - double comp; - - if (piv2 < (diskSorter.size() - 1)) { - for (int i = piv2; i < diskSorter.size(); i++) { - if (used.contains(String.valueOf(i))) { - comp = -1; - } else { - comp = DuplicateCheck.compareEntriesStrictly(tmpSorter.get(piv1), diskSorter.get(i)); - } - - if (comp > bestMatch) { - bestMatch = comp; - bestMatchI = i; - } - } - } - - if (bestMatch > MATCH_THRESHOLD) { - used.add(String.valueOf(bestMatchI)); - it.remove(); - - changes.add(new EntryChangeViewModel(bestFit(tmpSorter, memorySorter, piv1), tmpSorter.get(piv1), - diskSorter.get(bestMatchI))); - } else { - changes.add( - new EntryDeleteChangeViewModel(bestFit(tmpSorter, memorySorter, piv1), tmpSorter.get(piv1))); - } - - } - + if (diff.getNewString() == null) { + Optional current = databaseInMemory.getDatabase().getStringByName(diff.getOriginalString().getName()); + return new StringRemoveChangeViewModel(diff.getOriginalString(), current.orElse(null)); } - // Finally, look if there are still untouched entries in the disk database. These - // may have been added. - if (used.size() < diskSorter.size()) { - for (int i = 0; i < diskSorter.size(); i++) { - if (!used.contains(String.valueOf(i))) { - - // See if there is an identical dupe in the mem database: - boolean hasAlready = false; - for (int j = 0; j < memorySorter.size(); j++) { - if (DuplicateCheck.compareEntriesStrictly(memorySorter.get(j), diskSorter.get(i)) >= 1) { - hasAlready = true; - break; - } - } - if (!hasAlready) { - changes.add(new EntryAddChangeViewModel(diskSorter.get(i))); - } - } - } + if (diff.getOriginalString().getName().equals(diff.getNewString().getName())) { + Optional current = databaseInMemory.getDatabase().getStringByName(diff.getOriginalString().getName()); + return new StringChangeViewModel(current.orElse(null), diff.getOriginalString(), diff.getNewString().getContent()); } - } - private void scanPreamble(BibDatabase inMemory, BibDatabase onTmp, BibDatabase onDisk) { - String mem = inMemory.getPreamble().orElse(null); - Optional tmp = onTmp.getPreamble(); - Optional disk = onDisk.getPreamble(); - if (tmp.isPresent()) { - if (!disk.isPresent() || !tmp.equals(disk)) { - changes.add(new PreambleChangeViewModel(mem, disk.orElse(null))); - } - } else { - disk.ifPresent(diskContent -> changes.add(new PreambleChangeViewModel(mem, diskContent))); - } + Optional current = databaseInMemory.getDatabase().getStringByName(diff.getOriginalString().getName()); + return new StringNameChangeViewModel(current.orElse(null), diff.getOriginalString(), current.map(BibtexString::getName).orElse(""), diff.getNewString().getName()); } - private void scanStrings(BibDatabase inMem1, BibDatabase inTmp, BibDatabase onDisk) { - if (inTmp.hasNoStrings() && onDisk.hasNoStrings()) { - return; + private ChangeViewModel createBibEntryDiff(BibEntryDiff diff) { + if (diff.getOriginalEntry() == null) { + return new EntryAddChangeViewModel(diff.getNewEntry()); } - Set used = new HashSet<>(); - Set usedInMem = new HashSet<>(); - Set notMatched = new HashSet<>(inTmp.getStringCount()); - - // First try to match by string names. - mainLoop: - for (String key : inTmp.getStringKeySet()) { - BibtexString tmp = inTmp.getString(key); - - for (String diskId : onDisk.getStringKeySet()) { - if (!used.contains(diskId)) { - BibtexString disk = onDisk.getString(diskId); - if (disk.getName().equals(tmp.getName())) { - // We have found a string with a matching name. - if (!Objects.equals(tmp.getContent(), disk.getContent())) { - // But they have nonmatching contents, so we've found a change. - Optional mem = findString(inMem1, tmp.getName(), usedInMem); - if (mem.isPresent()) { - changes.add(new StringChangeViewModel(mem.get(), tmp, tmp.getName(), mem.get().getContent(), - disk.getContent())); - } else { - changes.add(new StringChangeViewModel(null, tmp, tmp.getName(), null, disk.getContent())); - } - } - used.add(diskId); - continue mainLoop; - } - - } - } - // If we get here, there was no match for this string. - notMatched.add(tmp.getId()); + if (diff.getNewEntry() == null) { + return new EntryDeleteChangeViewModel(bestFit(diff.getOriginalEntry(), databaseInMemory.getEntries()), diff.getOriginalEntry()); } - // See if we can detect a name change for those entries that we couldn't match. - if (!notMatched.isEmpty()) { - for (Iterator i = notMatched.iterator(); i.hasNext(); ) { - BibtexString tmp = inTmp.getString(i.next()); - - // If we get to this point, we found no string with matching name. See if we - // can find one with matching content. - for (String diskId : onDisk.getStringKeySet()) { - - if (!used.contains(diskId)) { - BibtexString disk = onDisk.getString(diskId); - - if (disk.getContent().equals(tmp.getContent())) { - // We have found a string with the same content. It cannot have the same - // name, or we would have found it above. - - // Try to find the matching one in memory: - BibtexString bsMem = null; - - for (String memId : inMem1.getStringKeySet()) { - BibtexString bsMemCandidate = inMem1.getString(memId); - if (bsMemCandidate.getContent().equals(disk.getContent()) - && !usedInMem.contains(memId)) { - usedInMem.add(memId); - bsMem = bsMemCandidate; - break; - } - } - - if (bsMem != null) { - changes.add(new StringNameChangeViewModel(bsMem, tmp, bsMem.getName(), tmp.getName(), - disk.getName(), tmp.getContent())); - i.remove(); - used.add(diskId); - } - - } - } - } - } - } - - if (!notMatched.isEmpty()) { - // Still one or more non-matched strings. So they must have been removed. - for (String notMatchedId : notMatched) { - BibtexString tmp = inTmp.getString(notMatchedId); - // The removed string is not removed from the mem version. - findString(inMem1, tmp.getName(), usedInMem).ifPresent( - x -> changes.add(new StringRemoveChangeViewModel(tmp, tmp, x))); - } - } - - // Finally, see if there are remaining strings in the disk database. They - // must have been added. - for (String diskId : onDisk.getStringKeySet()) { - if (!used.contains(diskId)) { - BibtexString disk = onDisk.getString(diskId); - used.add(diskId); - changes.add(new StringAddChangeViewModel(disk)); - } - } - } - - private static Optional findString(BibDatabase base, String name, Set used) { - if (!base.hasStringLabel(name)) { - return Optional.empty(); - } - for (String key : base.getStringKeySet()) { - BibtexString bs = base.getString(key); - if (bs.getName().equals(name) && !used.contains(key)) { - used.add(key); - return Optional.of(bs); - } - } - return Optional.empty(); - } - - /** - * This method only detects whether a change took place or not. It does not determine the type of change. This would - * be possible, but difficult to do properly, so I rather only report the change. - */ - private void scanGroups(MetaData inTemp, MetaData onDisk) { - final Optional groupsTmp = inTemp.getGroups(); - final Optional groupsDisk = onDisk.getGroups(); - if (!groupsTmp.isPresent() && !groupsDisk.isPresent()) { - return; - } - if (!groupsTmp.isPresent() || !groupsDisk.isPresent()) { - changes.add(new GroupChangeViewModel(groupsDisk.orElse(null), groupsTmp.orElse(null))); - return; - } - // Both present here - if (!groupsTmp.equals(groupsDisk)) { - changes.add(new GroupChangeViewModel(groupsDisk.get(), groupsTmp.get())); - } + return new EntryChangeViewModel(bestFit(diff.getOriginalEntry(), databaseInMemory.getEntries()), diff.getOriginalEntry(), diff.getNewEntry()); } @FunctionalInterface diff --git a/src/main/java/org/jabref/gui/collab/GroupChangeViewModel.java b/src/main/java/org/jabref/gui/collab/GroupChangeViewModel.java index ac3398f6ba7..68559347900 100644 --- a/src/main/java/org/jabref/gui/collab/GroupChangeViewModel.java +++ b/src/main/java/org/jabref/gui/collab/GroupChangeViewModel.java @@ -7,6 +7,7 @@ import org.jabref.gui.groups.GroupTreeNodeViewModel; import org.jabref.gui.groups.UndoableModifySubtree; import org.jabref.gui.undo.NamedCompound; +import org.jabref.logic.bibtex.comparator.GroupDiff; import org.jabref.logic.groups.DefaultGroupsFactory; import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabase; @@ -18,11 +19,11 @@ class GroupChangeViewModel extends ChangeViewModel { private final GroupTreeNode tmpGroupRoot; - public GroupChangeViewModel(GroupTreeNode changedGroups, GroupTreeNode tmpGroupRoot) { - super(changedGroups == null ? Localization.lang("Removed all groups") : Localization + public GroupChangeViewModel(GroupDiff diff) { + super(diff.getOriginalGroupRoot() == null ? Localization.lang("Removed all groups") : Localization .lang("Modified groups tree")); - this.changedGroups = changedGroups; - this.tmpGroupRoot = tmpGroupRoot; + this.changedGroups = diff.getOriginalGroupRoot(); + this.tmpGroupRoot = diff.getNewGroupRoot(); } @Override diff --git a/src/main/java/org/jabref/gui/collab/MetaDataChangeViewModel.java b/src/main/java/org/jabref/gui/collab/MetaDataChangeViewModel.java index 5ac134ac7e9..307fba9f4a0 100644 --- a/src/main/java/org/jabref/gui/collab/MetaDataChangeViewModel.java +++ b/src/main/java/org/jabref/gui/collab/MetaDataChangeViewModel.java @@ -5,6 +5,7 @@ import org.jabref.gui.BasePanel; import org.jabref.gui.undo.NamedCompound; +import org.jabref.logic.bibtex.comparator.MetaDataDiff; import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabase; import org.jabref.model.metadata.MetaData; @@ -19,10 +20,10 @@ class MetaDataChangeViewModel extends ChangeViewModel { private final MetaData originalMetaData; private final MetaData newMetaData; - public MetaDataChangeViewModel(MetaData originalMetaData, MetaData newMetaData) { + public MetaDataChangeViewModel(MetaData originalMetaData, MetaDataDiff metaDataDiff) { super(Localization.lang("Metadata change")); this.originalMetaData = originalMetaData; - this.newMetaData = newMetaData; + this.newMetaData = metaDataDiff.getNewMetaData(); infoPane.setText("" + Localization.lang("Metadata change") + ""); } diff --git a/src/main/java/org/jabref/gui/collab/PreambleChangeViewModel.java b/src/main/java/org/jabref/gui/collab/PreambleChangeViewModel.java index e207c451c3e..47946d0196e 100644 --- a/src/main/java/org/jabref/gui/collab/PreambleChangeViewModel.java +++ b/src/main/java/org/jabref/gui/collab/PreambleChangeViewModel.java @@ -6,8 +6,10 @@ import org.jabref.gui.BasePanel; import org.jabref.gui.undo.NamedCompound; import org.jabref.gui.undo.UndoablePreambleChange; +import org.jabref.logic.bibtex.comparator.PreambleDiff; import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabase; +import org.jabref.model.strings.StringUtil; class PreambleChangeViewModel extends ChangeViewModel { @@ -17,21 +19,21 @@ class PreambleChangeViewModel extends ChangeViewModel { private final JScrollPane sp = new JScrollPane(tp); - public PreambleChangeViewModel(String mem, String disk) { + public PreambleChangeViewModel(String mem, PreambleDiff diff) { super(Localization.lang("Changed preamble")); - this.disk = disk; + this.disk = diff.getNewPreamble(); this.mem = mem; StringBuilder text = new StringBuilder(34); text.append("

").append(Localization.lang("Changed preamble")).append("

"); - if ((disk != null) && !disk.isEmpty()) { + if (StringUtil.isNotBlank(disk)) { text.append("

").append(Localization.lang("Value set externally")).append(":

" + "").append(disk).append(""); } else { text.append("

").append(Localization.lang("Value cleared externally")).append("

"); } - if ((mem != null) && !mem.isEmpty()) { + if (StringUtil.isNotBlank(mem)) { text.append("

").append(Localization.lang("Current value")).append(":

" + "").append(mem).append(""); } diff --git a/src/main/java/org/jabref/gui/collab/StringChangeViewModel.java b/src/main/java/org/jabref/gui/collab/StringChangeViewModel.java index 8bf508382d5..003dd5fde5c 100644 --- a/src/main/java/org/jabref/gui/collab/StringChangeViewModel.java +++ b/src/main/java/org/jabref/gui/collab/StringChangeViewModel.java @@ -19,7 +19,6 @@ class StringChangeViewModel extends ChangeViewModel { private static final Log LOGGER = LogFactory.getLog(StringChangeViewModel.class); private final BibtexString string; - private final String mem; private final String disk; private final String label; @@ -29,12 +28,11 @@ class StringChangeViewModel extends ChangeViewModel { private final BibtexString tmpString; - public StringChangeViewModel(BibtexString string, BibtexString tmpString, String label, String mem, String disk) { - super(Localization.lang("Modified string") + ": '" + label + '\''); + public StringChangeViewModel(BibtexString string, BibtexString tmpString, String disk) { + super(Localization.lang("Modified string") + ": '" + tmpString.getName() + '\''); this.tmpString = tmpString; this.string = string; - this.label = label; - this.mem = mem; + this.label = tmpString.getName(); this.disk = disk; StringBuilder sb = new StringBuilder(46); @@ -66,6 +64,7 @@ public boolean makeChange(BasePanel panel, BibDatabase secondary, NamedCompound LOGGER.info("Error: could not add string '" + bs.getName() + "': " + ex.getMessage(), ex); } } else { + String mem = string.getContent(); string.setContent(disk); undoEdit.addEdit(new UndoableStringChange(panel, string, false, mem, disk)); } diff --git a/src/main/java/org/jabref/gui/collab/StringNameChangeViewModel.java b/src/main/java/org/jabref/gui/collab/StringNameChangeViewModel.java index cafba33fecf..98170b28fed 100644 --- a/src/main/java/org/jabref/gui/collab/StringNameChangeViewModel.java +++ b/src/main/java/org/jabref/gui/collab/StringNameChangeViewModel.java @@ -26,12 +26,11 @@ class StringNameChangeViewModel extends ChangeViewModel { private final BibtexString tmpString; - public StringNameChangeViewModel(BibtexString string, BibtexString tmpString, - String mem, String tmp, String disk, String content) { - super(Localization.lang("Renamed string") + ": '" + tmp + '\''); + public StringNameChangeViewModel(BibtexString string, BibtexString tmpString, String mem, String disk) { + super(Localization.lang("Renamed string") + ": '" + tmpString.getName() + '\''); this.tmpString = tmpString; this.string = string; - this.content = content; + this.content = tmpString.getContent(); this.mem = mem; this.disk = disk; diff --git a/src/main/java/org/jabref/gui/collab/StringRemoveChangeViewModel.java b/src/main/java/org/jabref/gui/collab/StringRemoveChangeViewModel.java index 4e1bb84b6ac..c47f6134594 100644 --- a/src/main/java/org/jabref/gui/collab/StringRemoveChangeViewModel.java +++ b/src/main/java/org/jabref/gui/collab/StringRemoveChangeViewModel.java @@ -21,12 +21,8 @@ class StringRemoveChangeViewModel extends ChangeViewModel { private final InfoPane tp = new InfoPane(); private final JScrollPane sp = new JScrollPane(tp); - private final BibtexString tmpString; - - - public StringRemoveChangeViewModel(BibtexString string, BibtexString tmpString, BibtexString inMem) { + public StringRemoveChangeViewModel(BibtexString string, BibtexString inMem) { super(Localization.lang("Removed string") + ": '" + string.getName() + '\''); - this.tmpString = tmpString; this.string = string; this.inMem = inMem; // Holds the version in memory. Check if it has been modified...? @@ -46,7 +42,7 @@ public boolean makeChange(BasePanel panel, BibDatabase secondary, NamedCompound } // Update tmp database: - secondary.removeString(tmpString.getId()); + secondary.removeString(string.getId()); return true; } diff --git a/src/main/java/org/jabref/logic/bibtex/comparator/BibDatabaseDiff.java b/src/main/java/org/jabref/logic/bibtex/comparator/BibDatabaseDiff.java new file mode 100644 index 00000000000..1ce0a2659d1 --- /dev/null +++ b/src/main/java/org/jabref/logic/bibtex/comparator/BibDatabaseDiff.java @@ -0,0 +1,144 @@ +package org.jabref.logic.bibtex.comparator; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import org.jabref.logic.bibtex.DuplicateCheck; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.FieldName; + +public class BibDatabaseDiff { + + private static final double MATCH_THRESHOLD = 0.4; + private final Optional metaDataDiff; + private final Optional preambleDiff; + private final List bibStringDiffs; + private final List entryDiffs; + + private BibDatabaseDiff(BibDatabaseContext originalDatabase, BibDatabaseContext newDatabase) { + metaDataDiff = MetaDataDiff.compare(originalDatabase.getMetaData(), newDatabase.getMetaData()); + preambleDiff = PreambleDiff.compare(originalDatabase, newDatabase); + bibStringDiffs = BibStringDiff.compare(originalDatabase.getDatabase(), newDatabase.getDatabase()); + + // Sort both databases according to a common sort key. + EntryComparator comparator = getEntryComparator(); + List originalEntriesSorted = originalDatabase.getDatabase().getEntriesSorted(comparator); + List newEntriesSorted = newDatabase.getDatabase().getEntriesSorted(comparator); + + entryDiffs = compareEntries(originalEntriesSorted, newEntriesSorted); + } + + private static EntryComparator getEntryComparator() { + EntryComparator comparator = new EntryComparator(false, true, FieldName.TITLE); + comparator = new EntryComparator(false, true, FieldName.AUTHOR, comparator); + comparator = new EntryComparator(false, true, FieldName.YEAR, comparator); + return comparator; + } + + private static List compareEntries(List originalEntries, List newEntries) { + List differences = new ArrayList<>(); + + // Create pointers that are incremented as the entries of each base are used in + // successive order from the beginning. Entries "further down" in the new database + // can also be matched. + int positionNew = 0; + + // Create a HashSet where we can put references to entries in the new + // database that we have matched. This is to avoid matching them twice. + Set used = new HashSet<>(newEntries.size()); + Set notMatched = new HashSet<>(originalEntries.size()); + + // Loop through the entries of the original database, looking for exact matches in the new one. + // We must finish scanning for exact matches before looking for near matches, to avoid an exact + // match being "stolen" from another entry. + mainLoop: + for (BibEntry originalEntry : originalEntries) { + // First check if the similarly placed entry in the other base matches exactly. + if (!used.contains(positionNew) && (positionNew < newEntries.size())) { + double score = DuplicateCheck.compareEntriesStrictly(originalEntry, newEntries.get(positionNew)); + if (score > 1) { + used.add(positionNew); + positionNew++; + continue; + } + } + // No? Then check if another entry matches exactly. + for (int i = positionNew + 1; i < newEntries.size(); i++) { + if (!used.contains(i)) { + double score = DuplicateCheck.compareEntriesStrictly(originalEntry, newEntries.get(i)); + if (score > 1) { + used.add(i); + continue mainLoop; + } + } + } + + // No? Add this entry to the list of non-matched entries. + notMatched.add(originalEntry); + } + + // Now we've found all exact matches, look through the remaining entries, looking for close matches. + for (Iterator iteratorNotMatched = notMatched.iterator(); iteratorNotMatched.hasNext(); ) { + BibEntry originalEntry = iteratorNotMatched.next(); + + // These two variables will keep track of which entry most closely matches the one we're looking at. + double bestMatch = 0; + int bestMatchIndex = -1; + if (positionNew < (newEntries.size() - 1)) { + for (int i = positionNew; i < newEntries.size(); i++) { + if (!used.contains(i)) { + double score = DuplicateCheck.compareEntriesStrictly(originalEntry, newEntries.get(i)); + if (score > bestMatch) { + bestMatch = score; + bestMatchIndex = i; + } + } + } + } + + if (bestMatch > MATCH_THRESHOLD) { + used.add(bestMatchIndex); + iteratorNotMatched.remove(); + + differences.add(new BibEntryDiff(originalEntry, newEntries.get(bestMatchIndex))); + } else { + differences.add(new BibEntryDiff(originalEntry, null)); + } + } + + + // Finally, look if there are still untouched entries in the new database. These may have been added. + for (int i = 0; i < newEntries.size(); i++) { + if (!used.contains(i)) { + differences.add(new BibEntryDiff(null, newEntries.get(i))); + } + } + + return differences; + } + + public static BibDatabaseDiff compare(BibDatabaseContext base, BibDatabaseContext changed) { + return new BibDatabaseDiff(base, changed); + } + + public Optional getMetaDataDifferences() { + return metaDataDiff; + } + + public Optional getPreambleDifferences() { + return preambleDiff; + } + + public List getBibStringDifferences() { + return bibStringDiffs; + } + + public List getEntryDifferences() { + return entryDiffs; + } +} diff --git a/src/main/java/org/jabref/logic/bibtex/comparator/BibEntryDiff.java b/src/main/java/org/jabref/logic/bibtex/comparator/BibEntryDiff.java new file mode 100644 index 00000000000..bcd098a3850 --- /dev/null +++ b/src/main/java/org/jabref/logic/bibtex/comparator/BibEntryDiff.java @@ -0,0 +1,21 @@ +package org.jabref.logic.bibtex.comparator; + +import org.jabref.model.entry.BibEntry; + +public class BibEntryDiff { + private final BibEntry originalEntry; + private final BibEntry newEntry; + + public BibEntryDiff(BibEntry originalEntry, BibEntry newEntry) { + this.originalEntry = originalEntry; + this.newEntry = newEntry; + } + + public BibEntry getOriginalEntry() { + return originalEntry; + } + + public BibEntry getNewEntry() { + return newEntry; + } +} diff --git a/src/main/java/org/jabref/logic/bibtex/comparator/BibStringDiff.java b/src/main/java/org/jabref/logic/bibtex/comparator/BibStringDiff.java new file mode 100644 index 00000000000..cc1c7fda689 --- /dev/null +++ b/src/main/java/org/jabref/logic/bibtex/comparator/BibStringDiff.java @@ -0,0 +1,89 @@ +package org.jabref.logic.bibtex.comparator; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +import org.jabref.model.database.BibDatabase; +import org.jabref.model.entry.BibtexString; + +public class BibStringDiff { + + private final BibtexString originalString; + private final BibtexString newString; + + private BibStringDiff(BibtexString originalString, BibtexString newString) { + this.originalString = originalString; + this.newString = newString; + } + + public static List compare(BibDatabase originalDatabase, BibDatabase newDatabase) { + if (originalDatabase.hasNoStrings() && newDatabase.hasNoStrings()) { + return Collections.emptyList(); + } + + List differences = new ArrayList<>(); + + Set used = new HashSet<>(); + Set notMatched = new HashSet<>(); + + // First try to match by string names. + for (BibtexString original : originalDatabase.getStringValues()) { + Optional match = newDatabase.getStringValues().stream() + .filter(test -> test.getName().equals(original.getName())) + .findAny(); + if (match.isPresent()) { + // We have found a string with a matching name. + if (!Objects.equals(original.getContent(), match.get().getContent())) { + // But they have non-matching contents, so we've found a change. + differences.add(new BibStringDiff(original, match.get())); + } + used.add(match.get()); + } else { + // No match for this string. + notMatched.add(original); + } + } + + // See if we can detect a name change for those entries that we couldn't match, based on their content + for (Iterator iterator = notMatched.iterator(); iterator.hasNext(); ) { + BibtexString original = iterator.next(); + + Optional match = newDatabase.getStringValues().stream() + .filter(test -> test.getName().equals(original.getName())) + .findAny(); + if (match.isPresent()) { + // We have found a string with the same content. It cannot have the same + // name, or we would have found it above. + differences.add(new BibStringDiff(original, match.get())); + iterator.remove(); + used.add(match.get()); + } + } + + // Strings that are still not found must have been removed. + for (BibtexString original : notMatched) { + differences.add(new BibStringDiff(original, null)); + } + + // Finally, see if there are remaining strings in the new database. They must have been added. + newDatabase.getStringValues().stream() + .filter(test -> !used.contains(test)) + .forEach(newString -> differences.add(new BibStringDiff(null, newString))); + + return differences; + } + + public BibtexString getOriginalString() { + return originalString; + } + + public BibtexString getNewString() { + return newString; + } +} diff --git a/src/main/java/org/jabref/logic/bibtex/comparator/GroupDiff.java b/src/main/java/org/jabref/logic/bibtex/comparator/GroupDiff.java new file mode 100644 index 00000000000..009172b5adb --- /dev/null +++ b/src/main/java/org/jabref/logic/bibtex/comparator/GroupDiff.java @@ -0,0 +1,39 @@ +package org.jabref.logic.bibtex.comparator; + +import java.util.Optional; + +import org.jabref.model.groups.GroupTreeNode; +import org.jabref.model.metadata.MetaData; + +public class GroupDiff { + private final GroupTreeNode originalGroupRoot; + private final GroupTreeNode newGroupRoot; + + private GroupDiff(GroupTreeNode originalGroupRoot, GroupTreeNode newGroupRoot) { + this.originalGroupRoot = originalGroupRoot; + this.newGroupRoot = newGroupRoot; + } + + /** + * This method only detects whether a change took place or not. It does not determine the type of change. This would + * be possible, but difficult to do properly, so we rather only report the change. + */ + public static Optional compare(MetaData originalMetaData, MetaData newMetaData) { + final Optional originalGroups = originalMetaData.getGroups(); + final Optional newGroups = newMetaData.getGroups(); + + if (!originalGroups.equals(newGroups)) { + return Optional.of(new GroupDiff(newGroups.orElse(null), originalGroups.orElse(null))); + } else { + return Optional.empty(); + } + } + + public GroupTreeNode getOriginalGroupRoot() { + return originalGroupRoot; + } + + public GroupTreeNode getNewGroupRoot() { + return newGroupRoot; + } +} diff --git a/src/main/java/org/jabref/logic/bibtex/comparator/MetaDataDiff.java b/src/main/java/org/jabref/logic/bibtex/comparator/MetaDataDiff.java new file mode 100644 index 00000000000..d04249d6717 --- /dev/null +++ b/src/main/java/org/jabref/logic/bibtex/comparator/MetaDataDiff.java @@ -0,0 +1,32 @@ +package org.jabref.logic.bibtex.comparator; + +import java.util.Optional; + +import org.jabref.model.metadata.MetaData; + +public class MetaDataDiff { + + private final Optional groupDiff; + private MetaData newMetaData; + + private MetaDataDiff(MetaData originalMetaData, MetaData newMetaData) { + this.newMetaData = newMetaData; + this.groupDiff = GroupDiff.compare(originalMetaData, newMetaData); + } + + public static Optional compare(MetaData originalMetaData, MetaData newMetaData) { + if (originalMetaData.equals(newMetaData)) { + return Optional.empty(); + } else { + return Optional.of(new MetaDataDiff(originalMetaData, newMetaData)); + } + } + + public MetaData getNewMetaData() { + return newMetaData; + } + + public Optional getGroupDifferences() { + return groupDiff; + } +} diff --git a/src/main/java/org/jabref/logic/bibtex/comparator/PreambleDiff.java b/src/main/java/org/jabref/logic/bibtex/comparator/PreambleDiff.java new file mode 100644 index 00000000000..b7bb0f56d31 --- /dev/null +++ b/src/main/java/org/jabref/logic/bibtex/comparator/PreambleDiff.java @@ -0,0 +1,28 @@ +package org.jabref.logic.bibtex.comparator; + +import java.util.Optional; + +import org.jabref.model.database.BibDatabaseContext; + +public class PreambleDiff { + + private String newPreamble; + + private PreambleDiff(String newPreamble) { + this.newPreamble = newPreamble; + } + + public static Optional compare(BibDatabaseContext originalDatabase, BibDatabaseContext newDatabase) { + Optional originalPreamble = originalDatabase.getDatabase().getPreamble(); + Optional newPreamble = newDatabase.getDatabase().getPreamble(); + if (originalPreamble.equals(newPreamble)) { + return Optional.empty(); + } else { + return Optional.of(new PreambleDiff(newPreamble.orElse(""))); + } + } + + public String getNewPreamble() { + return newPreamble; + } +} diff --git a/src/main/java/org/jabref/model/database/BibDatabase.java b/src/main/java/org/jabref/model/database/BibDatabase.java index 6a067d783de..fda6483fada 100644 --- a/src/main/java/org/jabref/model/database/BibDatabase.java +++ b/src/main/java/org/jabref/model/database/BibDatabase.java @@ -317,6 +317,18 @@ public BibtexString getString(String id) { return bibtexStrings.get(id); } + /** + * Returns the string with the given name. + */ + public Optional getStringByName(String name) { + for (BibtexString string : getStringValues()) { + if (string.getName().equals(name)) { + return Optional.of(string); + } + } + return Optional.empty(); + } + /** * Returns the number of strings. */ From 7ff48211434475c6331e7c9f87165804e13a4ad4 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Thu, 26 Oct 2017 00:16:54 +0800 Subject: [PATCH 05/22] Delete unused code --- .../java/org/jabref/model/database/BibDatabase.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/main/java/org/jabref/model/database/BibDatabase.java b/src/main/java/org/jabref/model/database/BibDatabase.java index fda6483fada..a5df29c3157 100644 --- a/src/main/java/org/jabref/model/database/BibDatabase.java +++ b/src/main/java/org/jabref/model/database/BibDatabase.java @@ -352,18 +352,6 @@ public void copyPreamble(BibDatabase database) { setPreamble(database.getPreamble().orElse("")); } - /** - * Copies all Strings from another BibDatabase. - * - * @param database another BibDatabase - */ - public void copyStrings(BibDatabase database) { - for (String key : database.getStringKeySet()) { - BibtexString string = database.getString(key); - addString(string); - } - } - /** * Returns true if a string with the given label already exists. */ From dc3754060f0643b3a87f9411f011737c3265a9af Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Sat, 28 Oct 2017 11:12:22 +0800 Subject: [PATCH 06/22] Add proper equals to content selector --- .../model/metadata/ContentSelector.java | 26 ++++++++++++++++++- .../comparator/BibDatabaseDiffTest.java | 22 ++++++++++++++++ .../bibtex/comparator/MetaDataDiffTest.java | 23 ++++++++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 src/test/java/org/jabref/logic/bibtex/comparator/BibDatabaseDiffTest.java create mode 100644 src/test/java/org/jabref/logic/bibtex/comparator/MetaDataDiffTest.java diff --git a/src/main/java/org/jabref/model/metadata/ContentSelector.java b/src/main/java/org/jabref/model/metadata/ContentSelector.java index 32e8cf4438c..0ac73bf6167 100644 --- a/src/main/java/org/jabref/model/metadata/ContentSelector.java +++ b/src/main/java/org/jabref/model/metadata/ContentSelector.java @@ -1,19 +1,43 @@ package org.jabref.model.metadata; +import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Objects; public class ContentSelector { private final String fieldName; - private final List values; + public ContentSelector(String fieldName, String... values) { + this(fieldName, Arrays.asList(values)); + } + public ContentSelector(String fieldName, List values) { this.fieldName = fieldName; this.values = values; } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ContentSelector that = (ContentSelector) o; + return Objects.equals(fieldName, that.fieldName) && + Objects.equals(values, that.values); + } + + @Override + public int hashCode() { + return Objects.hash(fieldName, values); + } + public String getFieldName() { return fieldName; } diff --git a/src/test/java/org/jabref/logic/bibtex/comparator/BibDatabaseDiffTest.java b/src/test/java/org/jabref/logic/bibtex/comparator/BibDatabaseDiffTest.java new file mode 100644 index 00000000000..83ea84a5863 --- /dev/null +++ b/src/test/java/org/jabref/logic/bibtex/comparator/BibDatabaseDiffTest.java @@ -0,0 +1,22 @@ +package org.jabref.logic.bibtex.comparator; + +import java.util.Collections; +import java.util.Optional; + +import org.jabref.model.database.BibDatabaseContext; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class BibDatabaseDiffTest { + @Test + public void compareOfEmptyDatabasesReportsNoDifferences() throws Exception { + BibDatabaseDiff diff = BibDatabaseDiff.compare(new BibDatabaseContext(), new BibDatabaseContext()); + assertEquals(Optional.empty(), diff.getPreambleDifferences()); + assertEquals(Optional.empty(), diff.getMetaDataDifferences()); + assertEquals(Collections.emptyList(), diff.getBibStringDifferences()); + assertEquals(Collections.emptyList(), diff.getEntryDifferences()); + } + +} diff --git a/src/test/java/org/jabref/logic/bibtex/comparator/MetaDataDiffTest.java b/src/test/java/org/jabref/logic/bibtex/comparator/MetaDataDiffTest.java new file mode 100644 index 00000000000..e70035009c2 --- /dev/null +++ b/src/test/java/org/jabref/logic/bibtex/comparator/MetaDataDiffTest.java @@ -0,0 +1,23 @@ +package org.jabref.logic.bibtex.comparator; + +import java.util.Optional; + +import org.jabref.model.metadata.ContentSelector; +import org.jabref.model.metadata.MetaData; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class MetaDataDiffTest { + @Test + public void compareWithSameContentSelectorsDoesNotReportAnyDiffs() throws Exception { + MetaData one = new MetaData(); + one.addContentSelector(new ContentSelector("author", "first", "second")); + MetaData two = new MetaData(); + two.addContentSelector(new ContentSelector("author", "first", "second")); + + assertEquals(Optional.empty(), MetaDataDiff.compare(one, two)); + } + +} From efe2d8603e30a03e47227055301752550bc4aaf9 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Sat, 28 Oct 2017 11:26:21 +0800 Subject: [PATCH 07/22] Fix checkstyle --- .../org/jabref/logic/bibtex/comparator/BibDatabaseDiff.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/org/jabref/logic/bibtex/comparator/BibDatabaseDiff.java b/src/main/java/org/jabref/logic/bibtex/comparator/BibDatabaseDiff.java index 1ce0a2659d1..3f69c530a21 100644 --- a/src/main/java/org/jabref/logic/bibtex/comparator/BibDatabaseDiff.java +++ b/src/main/java/org/jabref/logic/bibtex/comparator/BibDatabaseDiff.java @@ -110,8 +110,7 @@ private static List compareEntries(List originalEntries, differences.add(new BibEntryDiff(originalEntry, null)); } } - - + // Finally, look if there are still untouched entries in the new database. These may have been added. for (int i = 0; i < newEntries.size(); i++) { if (!used.contains(i)) { From 9aa4ac56deafb512860cdfc44cb9c990ce848dc8 Mon Sep 17 00:00:00 2001 From: Patrick Scheibe Date: Sat, 28 Oct 2017 05:36:11 +0200 Subject: [PATCH 08/22] This additional style setting for IDEA (#3355) * This additional setting will prevent IDEA from putting the annotation in something like this @FXML private EditorTextArea textArea; on a separate line. * Update README.md --- config/IntelliJ Code Style.xml | 525 +++++++++++++++++---------------- config/README.md | 7 +- 2 files changed, 269 insertions(+), 263 deletions(-) diff --git a/config/IntelliJ Code Style.xml b/config/IntelliJ Code Style.xml index 4551d121847..da5e8ae88b5 100644 --- a/config/IntelliJ Code Style.xml +++ b/config/IntelliJ Code Style.xml @@ -1,4 +1,4 @@ - + + \ No newline at end of file diff --git a/config/README.md b/config/README.md index 057f389a28c..76ec69f0770 100644 --- a/config/README.md +++ b/config/README.md @@ -1,6 +1,9 @@ -# IntelliJ Configuration +# IntelliJ IDEA Code Style Configuration -1. Press Ctrl + Alt + S +IntelliJ IDEA comes with a powerful code formatter that helps you to keep the formatting consistent with the style JabRef uses. +Style-checks are done for each pull request and installing this cody style configuration helps you to ensure that this test passes. To install it, you need to do the following steps: + +1. Goto *Preferences* or press Ctrl + Alt + S (Cmd + , on Mac OS X) 2. Go to "Editor > Code Style" 3. Click the gear (right of "Scheme: ...") 4. Click "Import Scheme >" From 2de829e806fb37920552daff4ce3718ccdc8a0fe Mon Sep 17 00:00:00 2001 From: Christoph Date: Sat, 28 Oct 2017 20:28:00 +0200 Subject: [PATCH 09/22] Fix freezing when running cleanup file operations like rename (#3315) * Fix freezing when running cleanup file operations like rename * Remove DefaultTaskExecutor from setField Update call of submit * add default task executor to cleanup ops * fix checkstyle --- .../org/jabref/gui/actions/CleanupAction.java | 3 ++- .../gui/fieldeditors/LinkedFileViewModel.java | 1 + .../jabref/gui/util/DefaultTaskExecutor.java | 22 ++++++++++++++----- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/jabref/gui/actions/CleanupAction.java b/src/main/java/org/jabref/gui/actions/CleanupAction.java index ae4f08b580b..89e8d94ab90 100644 --- a/src/main/java/org/jabref/gui/actions/CleanupAction.java +++ b/src/main/java/org/jabref/gui/actions/CleanupAction.java @@ -11,6 +11,7 @@ import org.jabref.gui.cleanup.CleanupPresetPanel; import org.jabref.gui.undo.NamedCompound; import org.jabref.gui.undo.UndoableFieldChange; +import org.jabref.gui.util.DefaultTaskExecutor; import org.jabref.gui.util.component.CheckBoxMessage; import org.jabref.gui.worker.AbstractWorker; import org.jabref.logic.cleanup.CleanupPreset; @@ -144,7 +145,7 @@ private void doCleanup(CleanupPreset preset, BibEntry entry, NamedCompound ce) { // Create and run cleaner CleanupWorker cleaner = new CleanupWorker(panel.getBibDatabaseContext(), preferences.getCleanupPreferences( Globals.journalAbbreviationLoader)); - List changes = cleaner.cleanup(preset, entry); + List changes = DefaultTaskExecutor.runInJavaFXThread(() -> cleaner.cleanup(preset, entry)); unsuccessfulRenames = cleaner.getUnsuccessfulRenames(); diff --git a/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java index fa10c432611..007f0432d2c 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java @@ -183,6 +183,7 @@ public void rename() { if (confirm) { Optional fileConflictCheck = pdfCleanup.findExistingFile(linkedFile, entry); + performRenameWithConflictCheck(file, pdfCleanup, targetFileName, fileConflictCheck); } } else { diff --git a/src/main/java/org/jabref/gui/util/DefaultTaskExecutor.java b/src/main/java/org/jabref/gui/util/DefaultTaskExecutor.java index 210ce487584..b27eacdd861 100644 --- a/src/main/java/org/jabref/gui/util/DefaultTaskExecutor.java +++ b/src/main/java/org/jabref/gui/util/DefaultTaskExecutor.java @@ -23,11 +23,17 @@ public class DefaultTaskExecutor implements TaskExecutor { private static final Log LOGGER = LogFactory.getLog(DefaultTaskExecutor.class); - private ExecutorService executor = Executors.newFixedThreadPool(5); + private static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(5); public static V runInJavaFXThread(Callable callable) { FutureTask task = new FutureTask<>(callable); - Platform.runLater(task); + + if (!Platform.isFxApplicationThread()) { + Platform.runLater(task); + } else { + EXECUTOR.submit(task); + } + try { return task.get(); } catch (InterruptedException | ExecutionException e) { @@ -37,22 +43,26 @@ public static V runInJavaFXThread(Callable callable) { } public static void runInJavaFXThread(Runnable runnable) { - Platform.runLater(runnable); + if (!Platform.isFxApplicationThread()) { + Platform.runLater(runnable); + } else { + EXECUTOR.submit(runnable); + } } @Override public void execute(BackgroundTask task) { - executor.submit(getJavaFXTask(task)); + EXECUTOR.submit(getJavaFXTask(task)); } @Override public void execute(FileDownloadTask downloadTask) { - executor.submit(downloadTask); + EXECUTOR.submit(downloadTask); } @Override public void shutdown() { - executor.shutdownNow(); + EXECUTOR.shutdownNow(); } private Task getJavaFXTask(BackgroundTask task) { From 4178789ce3add3f8437151a5561365641f13eff3 Mon Sep 17 00:00:00 2001 From: Christoph Date: Sun, 29 Oct 2017 19:20:33 +0100 Subject: [PATCH 10/22] Refactorings (#3370) * Rework AutoSetFileLinks * Only add first found file * Improve magic AutoFileLinking in entry editor by using same logic as manually triggering it * Don't use paths.get * add changelog entry * replcaed some occurrences of vector with arrayList * fix checkstyle --- CHANGELOG.md | 4 +- .../jabref/gui/FindUnlinkedFilesDialog.java | 15 +- .../gui/actions/AutoLinkFilesAction.java | 2 +- .../gui/externalfiles/AutoSetLinks.java | 168 +++++++++--------- .../externalfiles/SynchronizeFileField.java | 2 +- .../LinkedFilesEditorViewModel.java | 22 ++- .../gui/groups/UndoableModifySubtree.java | 5 +- .../gui/importer/ImportInspectionDialog.java | 2 +- .../model/search/matchers/MatcherSet.java | 10 +- 9 files changed, 115 insertions(+), 115 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e43533d6ef..bf91c2e88ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,7 +36,9 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `# - We fixed an issue where JabRef would freeze when trying to replace the original entry after a merge with new information from identifiers like DOI/ISBN etc. [3294](https://github.com/JabRef/jabref/issues/3294) - We fixed an issue where JabRef would not show the translated content at some points, although there existed a translation - We fixed an issue where editing in the source tab would override content of other entries [#3352](https://github.com/JabRef/jabref/issues/3352#issue-268580818) -### Removed + - We fixed several issues with the automatic linking of files in the entry editor where files were not found or not correctly saved in the bibtex source [#3346](https://github.com/JabRef/jabref/issues/3346) + + ### Removed diff --git a/src/main/java/org/jabref/gui/FindUnlinkedFilesDialog.java b/src/main/java/org/jabref/gui/FindUnlinkedFilesDialog.java index 7a30dd412a9..fbf049a942d 100644 --- a/src/main/java/org/jabref/gui/FindUnlinkedFilesDialog.java +++ b/src/main/java/org/jabref/gui/FindUnlinkedFilesDialog.java @@ -31,7 +31,6 @@ import java.util.LinkedList; import java.util.List; import java.util.Optional; -import java.util.Vector; import java.util.concurrent.atomic.AtomicBoolean; import javax.swing.AbstractAction; @@ -973,11 +972,7 @@ private void createFileTypesCombobox() { List fileFilterList = creatorManager.getFileFilterList(); - Vector vector = new Vector<>(); - for (FileFilter fileFilter : fileFilterList) { - vector.add(fileFilter); - } - comboBoxFileTypeSelection = new JComboBox<>(vector); + comboBoxFileTypeSelection = new JComboBox<>(fileFilterList.toArray(new FileFilter[fileFilterList.size()])); comboBoxFileTypeSelection.setRenderer(new DefaultListCellRenderer() { @@ -1009,16 +1004,15 @@ private void createEntryTypesCombobox() { Iterator iterator = EntryTypes .getAllValues(frame.getCurrentBasePanel().getBibDatabaseContext().getMode()).iterator(); - Vector list = new Vector<>(); + List list = new ArrayList<>(); list.add( new BibtexEntryTypeWrapper(null)); while (iterator.hasNext()) { list.add(new BibtexEntryTypeWrapper(iterator.next())); } - comboBoxEntryTypeSelection = new JComboBox<>(list); + comboBoxEntryTypeSelection = new JComboBox<>(list.toArray(new BibtexEntryTypeWrapper[list.size()])); } - /** * Wrapper for displaying the Type {@link BibtexEntryType} in a Combobox. * @@ -1030,7 +1024,6 @@ private static class BibtexEntryTypeWrapper { private final EntryType entryType; - BibtexEntryTypeWrapper(EntryType bibtexType) { this.entryType = bibtexType; } @@ -1053,7 +1046,6 @@ public static class CheckableTreeNode extends DefaultMutableTreeNode { private boolean isSelected; private final JCheckBox checkbox; - public CheckableTreeNode(Object userObject) { super(userObject); checkbox = new JCheckBox(); @@ -1134,7 +1126,6 @@ public static class FileNodeWrapper { public final File file; public final int fileCount; - public FileNodeWrapper(File aFile) { this(aFile, 0); } diff --git a/src/main/java/org/jabref/gui/actions/AutoLinkFilesAction.java b/src/main/java/org/jabref/gui/actions/AutoLinkFilesAction.java index 3f946a096a2..db9931a8f00 100644 --- a/src/main/java/org/jabref/gui/actions/AutoLinkFilesAction.java +++ b/src/main/java/org/jabref/gui/actions/AutoLinkFilesAction.java @@ -40,7 +40,7 @@ public void actionPerformed(ActionEvent event) { } JDialog diag = new JDialog(JabRefGUI.getMainFrame(), true); final NamedCompound nc = new NamedCompound(Localization.lang("Automatically set file links")); - Runnable runnable = AutoSetLinks.autoSetLinks(entries, nc, null, null, + Runnable runnable = AutoSetLinks.autoSetLinks(entries, nc, null, JabRefGUI.getMainFrame().getCurrentBasePanel().getBibDatabaseContext(), e -> { if (e.getID() > 0) { // entry has been updated in Util.autoSetLinks, only treat nc and status message diff --git a/src/main/java/org/jabref/gui/externalfiles/AutoSetLinks.java b/src/main/java/org/jabref/gui/externalfiles/AutoSetLinks.java index 4e63fffcb00..eb218f7621d 100644 --- a/src/main/java/org/jabref/gui/externalfiles/AutoSetLinks.java +++ b/src/main/java/org/jabref/gui/externalfiles/AutoSetLinks.java @@ -3,8 +3,9 @@ import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -25,21 +26,26 @@ import org.jabref.gui.externalfiletype.ExternalFileType; import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.externalfiletype.UnknownExternalFileType; -import org.jabref.gui.filelist.FileListEntry; -import org.jabref.gui.filelist.FileListTableModel; import org.jabref.gui.undo.NamedCompound; import org.jabref.gui.undo.UndoableFieldChange; +import org.jabref.gui.util.DefaultTaskExecutor; import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.io.FileFinder; import org.jabref.logic.util.io.FileFinders; -import org.jabref.logic.util.io.FileUtil; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.FieldName; +import org.jabref.model.entry.FileFieldWriter; +import org.jabref.model.entry.LinkedFile; import org.jabref.model.util.FileHelper; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + public class AutoSetLinks { + private static final Log LOGGER = LogFactory.getLog(AutoSetLinks.class); + private AutoSetLinks() { } @@ -50,7 +56,7 @@ private AutoSetLinks() { * @param databaseContext the database for which links are set */ public static void autoSetLinks(List entries, BibDatabaseContext databaseContext) { - autoSetLinks(entries, null, null, null, databaseContext, null, null); + autoSetLinks(entries, null, null, databaseContext, null, null); } /** @@ -79,7 +85,7 @@ public static void autoSetLinks(List entries, BibDatabaseContext datab * @return the thread performing the automatically setting */ public static Runnable autoSetLinks(final List entries, final NamedCompound ce, - final Set changedEntries, final FileListTableModel singleTableModel, + final Set changedEntries, final BibDatabaseContext databaseContext, final ActionListener callback, final JDialog diag) { final Collection types = ExternalFileTypes.getInstance().getExternalFileTypeSelection(); if (diag != null) { @@ -95,97 +101,87 @@ public static Runnable autoSetLinks(final List entries, final NamedCom diag.setLocationRelativeTo(diag.getParent()); } - Runnable r = new Runnable() { - - @Override - public void run() { - // determine directories to search in - final List dirs = databaseContext.getFileDirectoriesAsPaths(Globals.prefs.getFileDirectoryPreferences()); - - // determine extensions - final List extensions = types.stream().map(ExternalFileType::getExtension).collect(Collectors.toList()); - - // Run the search operation: - FileFinder fileFinder = FileFinders.constructFromConfiguration(Globals.prefs.getAutoLinkPreferences()); - Map> result = fileFinder.findAssociatedFiles(entries, dirs, extensions); - - boolean foundAny = false; - // Iterate over the entries: - for (Entry> entryFilePair : result.entrySet()) { - FileListTableModel tableModel; - Optional oldVal = entryFilePair.getKey().getField(FieldName.FILE); - if (singleTableModel == null) { - tableModel = new FileListTableModel(); - oldVal.ifPresent(tableModel::setContent); - } else { - assert entries.size() == 1; - tableModel = singleTableModel; - } - List files = entryFilePair.getValue(); - for (Path file : files) { - file = FileUtil.shortenFileName(file, dirs); - boolean alreadyHas = false; - - for (int j = 0; j < tableModel.getRowCount(); j++) { - FileListEntry existingEntry = tableModel.getEntry(j); - if (Paths.get(existingEntry.getLink()).equals(file)) { - alreadyHas = true; - foundAny = true; - break; - } + Runnable r = () -> { + + // determine directories to search in + final List dirs = databaseContext.getFileDirectoriesAsPaths(Globals.prefs.getFileDirectoryPreferences()); + + // determine extensions + final List extensions = types.stream().map(ExternalFileType::getExtension).collect(Collectors.toList()); + + // Run the search operation: + FileFinder fileFinder = FileFinders.constructFromConfiguration(Globals.prefs.getAutoLinkPreferences()); + Map> result = fileFinder.findAssociatedFiles(entries, dirs, extensions); + + boolean foundAny = false; + // Iterate over the entries: + for (Entry> entryFilePair : result.entrySet()) { + Optional oldVal = entryFilePair.getKey().getField(FieldName.FILE); + + for (Path foundFile : entryFilePair.getValue()) { + boolean existingSameFile = entryFilePair.getKey().getFiles().stream() + .map(file -> file.findIn(dirs)) + .anyMatch(file -> { + try { + return file.isPresent() && Files.isSameFile(file.get(), foundFile); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return false; + }); + if (!existingSameFile) { + + foundAny = true; + Optional type = FileHelper.getFileExtension(foundFile) + .map(extension -> ExternalFileTypes.getInstance().getExternalFileTypeByExt(extension)) + .orElse(Optional.of(new UnknownExternalFileType(""))); + + String strType = type.isPresent() ? type.get().getName() : ""; + LinkedFile linkedFile = new LinkedFile("", foundFile.toString(), strType); + + DefaultTaskExecutor.runInJavaFXThread(() -> { + entryFilePair.getKey().addFile(linkedFile); + }); + + String newVal = FileFieldWriter.getStringRepresentation(linkedFile); + if (ce != null) { + // store undo information + UndoableFieldChange change = new UndoableFieldChange(entryFilePair.getKey(), + FieldName.FILE, oldVal.orElse(null), newVal); + ce.addEdit(change); } - if (!alreadyHas) { - foundAny = true; - Optional type = FileHelper.getFileExtension(file) - .map(extension -> ExternalFileTypes.getInstance().getExternalFileTypeByExt(extension)) - .orElse(Optional.of(new UnknownExternalFileType(""))); - FileListEntry flEntry = new FileListEntry("", file.toString(), type); - tableModel.addEntry(tableModel.getRowCount(), flEntry); - - String newVal = tableModel.getStringRepresentation(); - if (newVal.isEmpty()) { - newVal = null; - } - if (ce != null) { - // store undo information - UndoableFieldChange change = new UndoableFieldChange(entryFilePair.getKey(), - FieldName.FILE, oldVal.orElse(null), newVal); - ce.addEdit(change); - } - // hack: if table model is given, do NOT modify entry - if (singleTableModel == null) { - entryFilePair.getKey().setField(FieldName.FILE, newVal); - } - if (changedEntries != null) { - changedEntries.add(entryFilePair.getKey()); - } + + if (changedEntries != null) { + changedEntries.add(entryFilePair.getKey()); } + break; } - } - // handle callbacks and dialog - // FIXME: The ID signals if action was successful :/ - final int id = foundAny ? 1 : 0; - SwingUtilities.invokeLater(new Runnable() { + } - @Override - public void run() { - if (diag != null) { - diag.dispose(); - } - if (callback != null) { - callback.actionPerformed(new ActionEvent(this, id, "")); - } - } - }); } + final int id = foundAny ? 1 : 0; + SwingUtilities.invokeLater(() -> { + + if (diag != null) { + diag.dispose(); + } + if (callback != null) { + callback.actionPerformed(new ActionEvent(AutoSetLinks.class, id, "")); + } + + }); + }; + SwingUtilities.invokeLater(() -> { // show dialog which will be hidden when the task is done if (diag != null) { diag.setVisible(true); } }); + return r; } @@ -206,9 +202,9 @@ public void run() { * parameter can be null, which means that no progress update will be shown. * @return the runnable able to perform the automatically setting */ - public static Runnable autoSetLinks(final BibEntry entry, final FileListTableModel singleTableModel, + public static Runnable autoSetLinks(final BibEntry entry, final BibDatabaseContext databaseContext, final ActionListener callback, final JDialog diag) { - return autoSetLinks(Collections.singletonList(entry), null, null, singleTableModel, databaseContext, callback, + return autoSetLinks(Collections.singletonList(entry), null, null, databaseContext, callback, diag); } diff --git a/src/main/java/org/jabref/gui/externalfiles/SynchronizeFileField.java b/src/main/java/org/jabref/gui/externalfiles/SynchronizeFileField.java index 2cbda14fcea..7f8a5011fa7 100644 --- a/src/main/java/org/jabref/gui/externalfiles/SynchronizeFileField.java +++ b/src/main/java/org/jabref/gui/externalfiles/SynchronizeFileField.java @@ -120,7 +120,7 @@ public void run() { List entries = new ArrayList<>(sel); // Start the automatically setting process: - Runnable r = AutoSetLinks.autoSetLinks(entries, ce, changedEntries, null, panel.getBibDatabaseContext(), null, null); + Runnable r = AutoSetLinks.autoSetLinks(entries, ce, changedEntries, panel.getBibDatabaseContext(), null, null); JabRefExecutorService.INSTANCE.executeAndWait(r); } progress += sel.size() * weightAutoSet; diff --git a/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditorViewModel.java index 4ce91d23bf7..5ac697e8c24 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditorViewModel.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; @@ -165,14 +166,25 @@ private List findAssociatedNotLinkedFiles(BibEntry entry) { List newFiles = fileFinder.findAssociatedFiles(entry, dirs, extensions); List result = new ArrayList<>(); - for (Path newFile : newFiles) { - boolean alreadyLinked = files.get().stream() + for (Path foundFile : newFiles) { + + boolean existingSameFile = files.get().stream() .map(file -> file.findIn(dirs)) - .anyMatch(file -> file.isPresent() && file.get().equals(newFile)); - if (!alreadyLinked) { - LinkedFileViewModel newLinkedFile = new LinkedFileViewModel(fromFile(newFile, dirs), entry, databaseContext); + .anyMatch(file -> { + try { + return file.isPresent() && Files.isSameFile(file.get(), foundFile); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return false; + }); + + if (!existingSameFile) { + LinkedFileViewModel newLinkedFile = new LinkedFileViewModel(fromFile(foundFile, dirs), entry, databaseContext); newLinkedFile.markAsAutomaticallyFound(); result.add(newLinkedFile); + break; //only add first file, if it exists multiple times } } return result; diff --git a/src/main/java/org/jabref/gui/groups/UndoableModifySubtree.java b/src/main/java/org/jabref/gui/groups/UndoableModifySubtree.java index 88da8d43396..f8d83ed5c5a 100644 --- a/src/main/java/org/jabref/gui/groups/UndoableModifySubtree.java +++ b/src/main/java/org/jabref/gui/groups/UndoableModifySubtree.java @@ -1,7 +1,7 @@ package org.jabref.gui.groups; +import java.util.ArrayList; import java.util.List; -import java.util.Vector; import org.jabref.gui.undo.AbstractUndoableJabRefEdit; import org.jabref.model.groups.GroupTreeNode; @@ -14,10 +14,9 @@ public class UndoableModifySubtree extends AbstractUndoableJabRefEdit { /** The path to the global groups root node */ private final List m_subtreeRootPath; /** This holds the new subtree (the root's modified children) to allow redo. */ - private final List m_modifiedSubtree = new Vector<>(); + private final List m_modifiedSubtree = new ArrayList<>(); private final String m_name; - /** * * @param subtree diff --git a/src/main/java/org/jabref/gui/importer/ImportInspectionDialog.java b/src/main/java/org/jabref/gui/importer/ImportInspectionDialog.java index f17cb19f4e2..cdf00d5d921 100644 --- a/src/main/java/org/jabref/gui/importer/ImportInspectionDialog.java +++ b/src/main/java/org/jabref/gui/importer/ImportInspectionDialog.java @@ -1294,7 +1294,7 @@ public void actionPerformed(ActionEvent actionEvent) { // links: JDialog diag = new JDialog(ImportInspectionDialog.this, true); JabRefExecutorService.INSTANCE - .execute(AutoSetLinks.autoSetLinks(entry, localModel, bibDatabaseContext, e -> { + .execute(AutoSetLinks.autoSetLinks(entry, bibDatabaseContext, e -> { if (e.getID() > 0) { entries.getReadWriteLock().writeLock().lock(); diff --git a/src/main/java/org/jabref/model/search/matchers/MatcherSet.java b/src/main/java/org/jabref/model/search/matchers/MatcherSet.java index 934ea16b598..92e6e7974f3 100644 --- a/src/main/java/org/jabref/model/search/matchers/MatcherSet.java +++ b/src/main/java/org/jabref/model/search/matchers/MatcherSet.java @@ -1,33 +1,33 @@ package org.jabref.model.search.matchers; +import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.Vector; import org.jabref.model.search.SearchMatcher; public abstract class MatcherSet implements SearchMatcher { - protected final List matchers = new Vector<>(); + protected final List matchers = new ArrayList<>(); @Override public boolean equals(Object o) { if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { + if ((o == null) || (getClass() != o.getClass())) { return false; } MatcherSet that = (MatcherSet) o; - return matchers.equals(that.matchers); + return Objects.equals(matchers, that.matchers); } @Override public int hashCode() { - return matchers.hashCode(); + return Objects.hash(matchers); } public void addRule(SearchMatcher newRule) { From 0235f635293e72692c8766af5d449258ce87b1d6 Mon Sep 17 00:00:00 2001 From: Siedlerchr Date: Sun, 29 Oct 2017 19:30:17 +0100 Subject: [PATCH 11/22] revert wrongly commited changes --- .../gui/externalfiles/AutoSetLinks.java | 168 +++++++++--------- .../externalfiles/SynchronizeFileField.java | 2 +- .../LinkedFilesEditorViewModel.java | 22 +-- 3 files changed, 92 insertions(+), 100 deletions(-) diff --git a/src/main/java/org/jabref/gui/externalfiles/AutoSetLinks.java b/src/main/java/org/jabref/gui/externalfiles/AutoSetLinks.java index eb218f7621d..4e63fffcb00 100644 --- a/src/main/java/org/jabref/gui/externalfiles/AutoSetLinks.java +++ b/src/main/java/org/jabref/gui/externalfiles/AutoSetLinks.java @@ -3,9 +3,8 @@ import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -26,26 +25,21 @@ import org.jabref.gui.externalfiletype.ExternalFileType; import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.externalfiletype.UnknownExternalFileType; +import org.jabref.gui.filelist.FileListEntry; +import org.jabref.gui.filelist.FileListTableModel; import org.jabref.gui.undo.NamedCompound; import org.jabref.gui.undo.UndoableFieldChange; -import org.jabref.gui.util.DefaultTaskExecutor; import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.io.FileFinder; import org.jabref.logic.util.io.FileFinders; +import org.jabref.logic.util.io.FileUtil; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.FieldName; -import org.jabref.model.entry.FileFieldWriter; -import org.jabref.model.entry.LinkedFile; import org.jabref.model.util.FileHelper; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - public class AutoSetLinks { - private static final Log LOGGER = LogFactory.getLog(AutoSetLinks.class); - private AutoSetLinks() { } @@ -56,7 +50,7 @@ private AutoSetLinks() { * @param databaseContext the database for which links are set */ public static void autoSetLinks(List entries, BibDatabaseContext databaseContext) { - autoSetLinks(entries, null, null, databaseContext, null, null); + autoSetLinks(entries, null, null, null, databaseContext, null, null); } /** @@ -85,7 +79,7 @@ public static void autoSetLinks(List entries, BibDatabaseContext datab * @return the thread performing the automatically setting */ public static Runnable autoSetLinks(final List entries, final NamedCompound ce, - final Set changedEntries, + final Set changedEntries, final FileListTableModel singleTableModel, final BibDatabaseContext databaseContext, final ActionListener callback, final JDialog diag) { final Collection types = ExternalFileTypes.getInstance().getExternalFileTypeSelection(); if (diag != null) { @@ -101,87 +95,97 @@ public static Runnable autoSetLinks(final List entries, final NamedCom diag.setLocationRelativeTo(diag.getParent()); } - Runnable r = () -> { - - // determine directories to search in - final List dirs = databaseContext.getFileDirectoriesAsPaths(Globals.prefs.getFileDirectoryPreferences()); - - // determine extensions - final List extensions = types.stream().map(ExternalFileType::getExtension).collect(Collectors.toList()); - - // Run the search operation: - FileFinder fileFinder = FileFinders.constructFromConfiguration(Globals.prefs.getAutoLinkPreferences()); - Map> result = fileFinder.findAssociatedFiles(entries, dirs, extensions); - - boolean foundAny = false; - // Iterate over the entries: - for (Entry> entryFilePair : result.entrySet()) { - Optional oldVal = entryFilePair.getKey().getField(FieldName.FILE); - - for (Path foundFile : entryFilePair.getValue()) { - boolean existingSameFile = entryFilePair.getKey().getFiles().stream() - .map(file -> file.findIn(dirs)) - .anyMatch(file -> { - try { - return file.isPresent() && Files.isSameFile(file.get(), foundFile); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - return false; - }); - if (!existingSameFile) { - - foundAny = true; - Optional type = FileHelper.getFileExtension(foundFile) - .map(extension -> ExternalFileTypes.getInstance().getExternalFileTypeByExt(extension)) - .orElse(Optional.of(new UnknownExternalFileType(""))); - - String strType = type.isPresent() ? type.get().getName() : ""; - LinkedFile linkedFile = new LinkedFile("", foundFile.toString(), strType); - - DefaultTaskExecutor.runInJavaFXThread(() -> { - entryFilePair.getKey().addFile(linkedFile); - }); - - String newVal = FileFieldWriter.getStringRepresentation(linkedFile); - if (ce != null) { - // store undo information - UndoableFieldChange change = new UndoableFieldChange(entryFilePair.getKey(), - FieldName.FILE, oldVal.orElse(null), newVal); - ce.addEdit(change); + Runnable r = new Runnable() { + + @Override + public void run() { + // determine directories to search in + final List dirs = databaseContext.getFileDirectoriesAsPaths(Globals.prefs.getFileDirectoryPreferences()); + + // determine extensions + final List extensions = types.stream().map(ExternalFileType::getExtension).collect(Collectors.toList()); + + // Run the search operation: + FileFinder fileFinder = FileFinders.constructFromConfiguration(Globals.prefs.getAutoLinkPreferences()); + Map> result = fileFinder.findAssociatedFiles(entries, dirs, extensions); + + boolean foundAny = false; + // Iterate over the entries: + for (Entry> entryFilePair : result.entrySet()) { + FileListTableModel tableModel; + Optional oldVal = entryFilePair.getKey().getField(FieldName.FILE); + if (singleTableModel == null) { + tableModel = new FileListTableModel(); + oldVal.ifPresent(tableModel::setContent); + } else { + assert entries.size() == 1; + tableModel = singleTableModel; + } + List files = entryFilePair.getValue(); + for (Path file : files) { + file = FileUtil.shortenFileName(file, dirs); + boolean alreadyHas = false; + + for (int j = 0; j < tableModel.getRowCount(); j++) { + FileListEntry existingEntry = tableModel.getEntry(j); + if (Paths.get(existingEntry.getLink()).equals(file)) { + alreadyHas = true; + foundAny = true; + break; + } } - - if (changedEntries != null) { - changedEntries.add(entryFilePair.getKey()); + if (!alreadyHas) { + foundAny = true; + Optional type = FileHelper.getFileExtension(file) + .map(extension -> ExternalFileTypes.getInstance().getExternalFileTypeByExt(extension)) + .orElse(Optional.of(new UnknownExternalFileType(""))); + FileListEntry flEntry = new FileListEntry("", file.toString(), type); + tableModel.addEntry(tableModel.getRowCount(), flEntry); + + String newVal = tableModel.getStringRepresentation(); + if (newVal.isEmpty()) { + newVal = null; + } + if (ce != null) { + // store undo information + UndoableFieldChange change = new UndoableFieldChange(entryFilePair.getKey(), + FieldName.FILE, oldVal.orElse(null), newVal); + ce.addEdit(change); + } + // hack: if table model is given, do NOT modify entry + if (singleTableModel == null) { + entryFilePair.getKey().setField(FieldName.FILE, newVal); + } + if (changedEntries != null) { + changedEntries.add(entryFilePair.getKey()); + } } - break; } - - } - - } - final int id = foundAny ? 1 : 0; - SwingUtilities.invokeLater(() -> { - - if (diag != null) { - diag.dispose(); - } - if (callback != null) { - callback.actionPerformed(new ActionEvent(AutoSetLinks.class, id, "")); } - }); + // handle callbacks and dialog + // FIXME: The ID signals if action was successful :/ + final int id = foundAny ? 1 : 0; + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + if (diag != null) { + diag.dispose(); + } + if (callback != null) { + callback.actionPerformed(new ActionEvent(this, id, "")); + } + } + }); + } }; - SwingUtilities.invokeLater(() -> { // show dialog which will be hidden when the task is done if (diag != null) { diag.setVisible(true); } }); - return r; } @@ -202,9 +206,9 @@ public static Runnable autoSetLinks(final List entries, final NamedCom * parameter can be null, which means that no progress update will be shown. * @return the runnable able to perform the automatically setting */ - public static Runnable autoSetLinks(final BibEntry entry, + public static Runnable autoSetLinks(final BibEntry entry, final FileListTableModel singleTableModel, final BibDatabaseContext databaseContext, final ActionListener callback, final JDialog diag) { - return autoSetLinks(Collections.singletonList(entry), null, null, databaseContext, callback, + return autoSetLinks(Collections.singletonList(entry), null, null, singleTableModel, databaseContext, callback, diag); } diff --git a/src/main/java/org/jabref/gui/externalfiles/SynchronizeFileField.java b/src/main/java/org/jabref/gui/externalfiles/SynchronizeFileField.java index 7f8a5011fa7..2cbda14fcea 100644 --- a/src/main/java/org/jabref/gui/externalfiles/SynchronizeFileField.java +++ b/src/main/java/org/jabref/gui/externalfiles/SynchronizeFileField.java @@ -120,7 +120,7 @@ public void run() { List entries = new ArrayList<>(sel); // Start the automatically setting process: - Runnable r = AutoSetLinks.autoSetLinks(entries, ce, changedEntries, panel.getBibDatabaseContext(), null, null); + Runnable r = AutoSetLinks.autoSetLinks(entries, ce, changedEntries, null, panel.getBibDatabaseContext(), null, null); JabRefExecutorService.INSTANCE.executeAndWait(r); } progress += sel.size() * weightAutoSet; diff --git a/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditorViewModel.java index 5ac697e8c24..4ce91d23bf7 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditorViewModel.java @@ -3,7 +3,6 @@ import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; @@ -166,25 +165,14 @@ private List findAssociatedNotLinkedFiles(BibEntry entry) { List newFiles = fileFinder.findAssociatedFiles(entry, dirs, extensions); List result = new ArrayList<>(); - for (Path foundFile : newFiles) { - - boolean existingSameFile = files.get().stream() + for (Path newFile : newFiles) { + boolean alreadyLinked = files.get().stream() .map(file -> file.findIn(dirs)) - .anyMatch(file -> { - try { - return file.isPresent() && Files.isSameFile(file.get(), foundFile); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - return false; - }); - - if (!existingSameFile) { - LinkedFileViewModel newLinkedFile = new LinkedFileViewModel(fromFile(foundFile, dirs), entry, databaseContext); + .anyMatch(file -> file.isPresent() && file.get().equals(newFile)); + if (!alreadyLinked) { + LinkedFileViewModel newLinkedFile = new LinkedFileViewModel(fromFile(newFile, dirs), entry, databaseContext); newLinkedFile.markAsAutomaticallyFound(); result.add(newLinkedFile); - break; //only add first file, if it exists multiple times } } return result; From 551cb91be8370e26dfffedb5ca6414b6f51342a4 Mon Sep 17 00:00:00 2001 From: Christoph Date: Tue, 31 Oct 2017 11:09:20 +0100 Subject: [PATCH 12/22] Rework AutoSetFileLinks (#3368) * Rework AutoSetFileLinks * Only add first found file * Improve magic AutoFileLinking in entry editor by using same logic as manually triggering it * Don't use paths.get * add changelog entry * refactor TODO: remove duplicate findings * Use HashMap with BibEntry and LinkedFiles Only call setFiles when pressing F7 Fix javafx thread error * improve optional code use streams in externalfiletype * logging remove javadoc params * pass preferences to method * Return list of Linkedfiles and let caller handle bib entries Add test * Replace mocking of Jabrefs prefs with autolink prefs --- .../externalfiles/AutoSetFileLinksUtil.java | 68 ++++++++ .../gui/externalfiles/AutoSetLinks.java | 153 ++++++------------ .../gui/externalfiles/FindFullTextAction.java | 25 +-- .../externalfiles/SynchronizeFileField.java | 2 +- .../externalfiletype/ExternalFileTypes.java | 39 +---- .../LinkedFilesEditorViewModel.java | 26 ++- .../AutoSetFileLinksUtilTest.java | 55 +++++++ 7 files changed, 204 insertions(+), 164 deletions(-) create mode 100644 src/main/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtil.java create mode 100644 src/test/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtilTest.java diff --git a/src/main/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtil.java b/src/main/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtil.java new file mode 100644 index 00000000000..0fa1fce83f3 --- /dev/null +++ b/src/main/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtil.java @@ -0,0 +1,68 @@ +package org.jabref.gui.externalfiles; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.jabref.gui.externalfiletype.ExternalFileType; +import org.jabref.gui.externalfiletype.ExternalFileTypes; +import org.jabref.gui.externalfiletype.UnknownExternalFileType; +import org.jabref.logic.util.io.AutoLinkPreferences; +import org.jabref.logic.util.io.FileFinder; +import org.jabref.logic.util.io.FileFinders; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.LinkedFile; +import org.jabref.model.metadata.FileDirectoryPreferences; +import org.jabref.model.util.FileHelper; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +public class AutoSetFileLinksUtil { + + private static final Log LOGGER = LogFactory.getLog(AutoSetLinks.class); + + public List findassociatedNotLinkedFiles(BibEntry entry, BibDatabaseContext databaseContext, FileDirectoryPreferences fileDirPrefs, AutoLinkPreferences autoLinkPrefs, ExternalFileTypes externalFileTypes) { + List linkedFiles = new ArrayList<>(); + + List dirs = databaseContext.getFileDirectoriesAsPaths(fileDirPrefs); + List extensions = externalFileTypes.getExternalFileTypeSelection().stream().map(ExternalFileType::getExtension).collect(Collectors.toList()); + + // Run the search operation: + FileFinder fileFinder = FileFinders.constructFromConfiguration(autoLinkPrefs); + List result = fileFinder.findAssociatedFiles(entry, dirs, extensions); + + // Iterate over the entries: + + for (Path foundFile : result) { + boolean existingSameFile = entry.getFiles().stream() + .map(file -> file.findIn(dirs)) + .anyMatch(file -> { + try { + return file.isPresent() && Files.isSameFile(file.get(), foundFile); + } catch (IOException e) { + LOGGER.error("Problem with isSameFile", e); + } + return false; + }); + if (!existingSameFile) { + + Optional type = FileHelper.getFileExtension(foundFile) + .map(externalFileTypes::getExternalFileTypeByExt) + .orElse(Optional.of(new UnknownExternalFileType(""))); + + String strType = type.isPresent() ? type.get().getName() : ""; + + LinkedFile linkedFile = new LinkedFile("", foundFile.toString(), strType); + linkedFiles.add(linkedFile); + } + } + + return linkedFiles; + } +} diff --git a/src/main/java/org/jabref/gui/externalfiles/AutoSetLinks.java b/src/main/java/org/jabref/gui/externalfiles/AutoSetLinks.java index 4e63fffcb00..b8f15487205 100644 --- a/src/main/java/org/jabref/gui/externalfiles/AutoSetLinks.java +++ b/src/main/java/org/jabref/gui/externalfiles/AutoSetLinks.java @@ -3,16 +3,10 @@ import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Optional; import java.util.Set; -import java.util.stream.Collectors; import javax.swing.BorderFactory; import javax.swing.JDialog; @@ -24,19 +18,15 @@ import org.jabref.Globals; import org.jabref.gui.externalfiletype.ExternalFileType; import org.jabref.gui.externalfiletype.ExternalFileTypes; -import org.jabref.gui.externalfiletype.UnknownExternalFileType; -import org.jabref.gui.filelist.FileListEntry; -import org.jabref.gui.filelist.FileListTableModel; import org.jabref.gui.undo.NamedCompound; import org.jabref.gui.undo.UndoableFieldChange; +import org.jabref.gui.util.DefaultTaskExecutor; import org.jabref.logic.l10n.Localization; -import org.jabref.logic.util.io.FileFinder; -import org.jabref.logic.util.io.FileFinders; -import org.jabref.logic.util.io.FileUtil; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.FieldName; -import org.jabref.model.util.FileHelper; +import org.jabref.model.entry.FileFieldWriter; +import org.jabref.model.entry.LinkedFile; public class AutoSetLinks { @@ -50,7 +40,7 @@ private AutoSetLinks() { * @param databaseContext the database for which links are set */ public static void autoSetLinks(List entries, BibDatabaseContext databaseContext) { - autoSetLinks(entries, null, null, null, databaseContext, null, null); + autoSetLinks(entries, null, null, databaseContext, null, null); } /** @@ -64,12 +54,7 @@ public static void autoSetLinks(List entries, BibDatabaseContext datab * @param entries A collection of BibEntry objects to find links for. * @param ce A NamedCompound to add UndoEdit elements to. * @param changedEntries MODIFIED, optional. A Set of BibEntry objects to which all modified entries is added. - * This is used for status output and debugging - * @param singleTableModel UGLY HACK. The table model to insert links into. Already existing links are not - * duplicated or removed. This parameter has to be null if entries.count() != 1. The hack has been - * introduced as a bibtexentry does not (yet) support the function getListTableModel() and the - * FileListEntryEditor editor holds an instance of that table model and does not reconstruct it after the - * search has succeeded. + * @param databaseContext The database providing the relevant file directory, if any. * @param callback An ActionListener that is notified (on the event dispatch thread) when the search is finished. * The ActionEvent has id=0 if no new links were added, and id=1 if one or more links were added. This @@ -79,7 +64,7 @@ public static void autoSetLinks(List entries, BibDatabaseContext datab * @return the thread performing the automatically setting */ public static Runnable autoSetLinks(final List entries, final NamedCompound ce, - final Set changedEntries, final FileListTableModel singleTableModel, + final Set changedEntries, final BibDatabaseContext databaseContext, final ActionListener callback, final JDialog diag) { final Collection types = ExternalFileTypes.getInstance().getExternalFileTypeSelection(); if (diag != null) { @@ -95,97 +80,58 @@ public static Runnable autoSetLinks(final List entries, final NamedCom diag.setLocationRelativeTo(diag.getParent()); } - Runnable r = new Runnable() { - - @Override - public void run() { - // determine directories to search in - final List dirs = databaseContext.getFileDirectoriesAsPaths(Globals.prefs.getFileDirectoryPreferences()); - - // determine extensions - final List extensions = types.stream().map(ExternalFileType::getExtension).collect(Collectors.toList()); - - // Run the search operation: - FileFinder fileFinder = FileFinders.constructFromConfiguration(Globals.prefs.getAutoLinkPreferences()); - Map> result = fileFinder.findAssociatedFiles(entries, dirs, extensions); - - boolean foundAny = false; - // Iterate over the entries: - for (Entry> entryFilePair : result.entrySet()) { - FileListTableModel tableModel; - Optional oldVal = entryFilePair.getKey().getField(FieldName.FILE); - if (singleTableModel == null) { - tableModel = new FileListTableModel(); - oldVal.ifPresent(tableModel::setContent); - } else { - assert entries.size() == 1; - tableModel = singleTableModel; + Runnable r = () -> { + boolean foundAny = false; + AutoSetFileLinksUtil util = new AutoSetFileLinksUtil(); + + for (BibEntry entry : entries) { + + List linkedFiles = util.findassociatedNotLinkedFiles(entry, databaseContext, Globals.prefs.getFileDirectoryPreferences(), Globals.prefs.getAutoLinkPreferences(), ExternalFileTypes.getInstance()); + + if (ce != null) { + for (LinkedFile linkedFile : linkedFiles) { + // store undo information + String newVal = FileFieldWriter.getStringRepresentation(linkedFile); + + String oldVal = entry.getField(FieldName.FILE).orElse(null); + + UndoableFieldChange fieldChange = new UndoableFieldChange(entry, FieldName.FILE, oldVal, newVal); + ce.addEdit(fieldChange); + + DefaultTaskExecutor.runInJavaFXThread(() -> { + entry.addFile(linkedFile); + }); + foundAny = true; } - List files = entryFilePair.getValue(); - for (Path file : files) { - file = FileUtil.shortenFileName(file, dirs); - boolean alreadyHas = false; - - for (int j = 0; j < tableModel.getRowCount(); j++) { - FileListEntry existingEntry = tableModel.getEntry(j); - if (Paths.get(existingEntry.getLink()).equals(file)) { - alreadyHas = true; - foundAny = true; - break; - } - } - if (!alreadyHas) { - foundAny = true; - Optional type = FileHelper.getFileExtension(file) - .map(extension -> ExternalFileTypes.getInstance().getExternalFileTypeByExt(extension)) - .orElse(Optional.of(new UnknownExternalFileType(""))); - FileListEntry flEntry = new FileListEntry("", file.toString(), type); - tableModel.addEntry(tableModel.getRowCount(), flEntry); - - String newVal = tableModel.getStringRepresentation(); - if (newVal.isEmpty()) { - newVal = null; - } - if (ce != null) { - // store undo information - UndoableFieldChange change = new UndoableFieldChange(entryFilePair.getKey(), - FieldName.FILE, oldVal.orElse(null), newVal); - ce.addEdit(change); - } - // hack: if table model is given, do NOT modify entry - if (singleTableModel == null) { - entryFilePair.getKey().setField(FieldName.FILE, newVal); - } - if (changedEntries != null) { - changedEntries.add(entryFilePair.getKey()); - } - } + + if (changedEntries != null) { + changedEntries.add(entry); } } - // handle callbacks and dialog - // FIXME: The ID signals if action was successful :/ - final int id = foundAny ? 1 : 0; - SwingUtilities.invokeLater(new Runnable() { - - @Override - public void run() { - if (diag != null) { - diag.dispose(); - } - if (callback != null) { - callback.actionPerformed(new ActionEvent(this, id, "")); - } - } - }); } + + final int id = foundAny ? 1 : 0; + SwingUtilities.invokeLater(() -> { + + if (diag != null) { + diag.dispose(); + } + if (callback != null) { + callback.actionPerformed(new ActionEvent(AutoSetLinks.class, id, "")); + } + + }); + }; + SwingUtilities.invokeLater(() -> { // show dialog which will be hidden when the task is done if (diag != null) { diag.setVisible(true); } }); + return r; } @@ -194,8 +140,7 @@ public void run() { * of external file types. The entry itself is not modified. The entry's bibtex key must have been set. * * @param entry The BibEntry to find links for. - * @param singleTableModel The table model to insert links into. Already existing links are not duplicated or - * removed. + * @param databaseContext The database providing the relevant file directory, if any. * @param callback An ActionListener that is notified (on the event dispatch thread) when the search is finished. * The ActionEvent has id=0 if no new links were added, and id=1 if one or more links were added. This @@ -206,9 +151,9 @@ public void run() { * parameter can be null, which means that no progress update will be shown. * @return the runnable able to perform the automatically setting */ - public static Runnable autoSetLinks(final BibEntry entry, final FileListTableModel singleTableModel, + public static Runnable autoSetLinks(final BibEntry entry, final BibDatabaseContext databaseContext, final ActionListener callback, final JDialog diag) { - return autoSetLinks(Collections.singletonList(entry), null, null, singleTableModel, databaseContext, callback, + return autoSetLinks(Collections.singletonList(entry), null, null, databaseContext, callback, diag); } diff --git a/src/main/java/org/jabref/gui/externalfiles/FindFullTextAction.java b/src/main/java/org/jabref/gui/externalfiles/FindFullTextAction.java index 5bf278dcd9f..aaf44053987 100644 --- a/src/main/java/org/jabref/gui/externalfiles/FindFullTextAction.java +++ b/src/main/java/org/jabref/gui/externalfiles/FindFullTextAction.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.net.URL; +import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -14,6 +15,7 @@ import org.jabref.Globals; import org.jabref.gui.BasePanel; import org.jabref.gui.undo.UndoableFieldChange; +import org.jabref.gui.util.DefaultTaskExecutor; import org.jabref.gui.worker.AbstractWorker; import org.jabref.logic.importer.FulltextFetchers; import org.jabref.logic.l10n.Localization; @@ -81,9 +83,9 @@ public void update() { BibEntry entry = download.getValue(); Optional result = download.getKey(); if (result.isPresent()) { - List dirs = basePanel.getBibDatabaseContext() - .getFileDirectories(Globals.prefs.getFileDirectoryPreferences()); - if (dirs.isEmpty()) { + Optional dir = basePanel.getBibDatabaseContext().getFirstExistingFileDir(Globals.prefs.getFileDirectoryPreferences()); + + if (!dir.isPresent()) { JOptionPane.showMessageDialog(basePanel.frame(), Localization.lang("Main file directory not set!") + " " + Localization.lang("Preferences") + " -> " + Localization.lang("File"), @@ -94,13 +96,16 @@ public void update() { basePanel.getBibDatabaseContext(), entry); try { def.download(result.get(), file -> { - Optional fieldChange = entry.addFile(file); - if (fieldChange.isPresent()) { - UndoableFieldChange edit = new UndoableFieldChange(entry, FieldName.FILE, - entry.getField(FieldName.FILE).orElse(null), fieldChange.get().getNewValue()); - basePanel.getUndoManager().addEdit(edit); - basePanel.markBaseChanged(); - } + DefaultTaskExecutor.runInJavaFXThread(() -> { + Optional fieldChange = entry.addFile(file); + if (fieldChange.isPresent()) { + UndoableFieldChange edit = new UndoableFieldChange(entry, FieldName.FILE, + entry.getField(FieldName.FILE).orElse(null), fieldChange.get().getNewValue()); + basePanel.getUndoManager().addEdit(edit); + basePanel.markBaseChanged(); + } + }); + }); } catch (IOException e) { LOGGER.warn("Problem downloading file", e); diff --git a/src/main/java/org/jabref/gui/externalfiles/SynchronizeFileField.java b/src/main/java/org/jabref/gui/externalfiles/SynchronizeFileField.java index 2cbda14fcea..7f8a5011fa7 100644 --- a/src/main/java/org/jabref/gui/externalfiles/SynchronizeFileField.java +++ b/src/main/java/org/jabref/gui/externalfiles/SynchronizeFileField.java @@ -120,7 +120,7 @@ public void run() { List entries = new ArrayList<>(sel); // Start the automatically setting process: - Runnable r = AutoSetLinks.autoSetLinks(entries, ce, changedEntries, null, panel.getBibDatabaseContext(), null, null); + Runnable r = AutoSetLinks.autoSetLinks(entries, ce, changedEntries, panel.getBibDatabaseContext(), null, null); JabRefExecutorService.INSTANCE.executeAndWait(r); } progress += sel.size() * weightAutoSet; diff --git a/src/main/java/org/jabref/gui/externalfiletype/ExternalFileTypes.java b/src/main/java/org/jabref/gui/externalfiletype/ExternalFileTypes.java index 8b9d9193204..702b47048bd 100644 --- a/src/main/java/org/jabref/gui/externalfiletype/ExternalFileTypes.java +++ b/src/main/java/org/jabref/gui/externalfiletype/ExternalFileTypes.java @@ -17,7 +17,8 @@ import org.jabref.model.util.FileHelper; import org.jabref.preferences.JabRefPreferences; -public final class ExternalFileTypes { +//Do not make this class final, as it otherwise can't be mocked for tests +public class ExternalFileTypes { // This String is used in the encoded list in prefs of external file type // modifications, in order to indicate a removed default file type: @@ -115,10 +116,9 @@ public Set getExternalFileTypeSelection() { * @return The ExternalFileType registered, or null if none. */ public Optional getExternalFileTypeByName(String name) { - for (ExternalFileType type : externalFileTypes) { - if (type.getName().equals(name)) { - return Optional.of(type); - } + Optional externalFileType = externalFileTypes.stream().filter(type -> type.getExtension().equals(name)).findFirst(); + if (externalFileType.isPresent()) { + return externalFileType; } // Return an instance that signifies an unknown file type: return Optional.of(new UnknownExternalFileType(name)); @@ -131,12 +131,7 @@ public Optional getExternalFileTypeByName(String name) { * @return The ExternalFileType registered, or null if none. */ public Optional getExternalFileTypeByExt(String extension) { - for (ExternalFileType type : externalFileTypes) { - if (type.getExtension().equalsIgnoreCase(extension)) { - return Optional.of(type); - } - } - return Optional.empty(); + return externalFileTypes.stream().filter(type -> type.getExtension().equalsIgnoreCase(extension)).findFirst(); } /** @@ -146,27 +141,7 @@ public Optional getExternalFileTypeByExt(String extension) { * @return true if an ExternalFileType with the extension exists, false otherwise */ public boolean isExternalFileTypeByExt(String extension) { - for (ExternalFileType type : externalFileTypes) { - if (type.getExtension().equalsIgnoreCase(extension)) { - return true; - } - } - return false; - } - - /** - * Look up the external file type name registered for this extension, if any. - * - * @param extension The file extension. - * @return The name of the ExternalFileType registered, or null if none. - */ - public String getExternalFileTypeNameByExt(String extension) { - for (ExternalFileType type : externalFileTypes) { - if (type.getExtension().equalsIgnoreCase(extension)) { - return type.getName(); - } - } - return ""; + return externalFileTypes.stream().anyMatch(type -> type.getExtension().equalsIgnoreCase(extension)); } /** diff --git a/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditorViewModel.java index 4ce91d23bf7..5860b731501 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditorViewModel.java @@ -20,6 +20,7 @@ import org.jabref.Globals; import org.jabref.gui.DialogService; import org.jabref.gui.autocompleter.AutoCompleteSuggestionProvider; +import org.jabref.gui.externalfiles.AutoSetFileLinksUtil; import org.jabref.gui.externalfiles.DownloadExternalFile; import org.jabref.gui.externalfiles.FileDownloadTask; import org.jabref.gui.externalfiletype.ExternalFileType; @@ -34,8 +35,6 @@ import org.jabref.logic.l10n.Localization; import org.jabref.logic.net.URLDownload; import org.jabref.logic.util.OS; -import org.jabref.logic.util.io.FileFinder; -import org.jabref.logic.util.io.FileFinders; import org.jabref.logic.util.io.FileUtil; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; @@ -157,24 +156,17 @@ public void bindToEntry(BibEntry entry) { * Find files that are probably associated to the given entry but not yet linked. */ private List findAssociatedNotLinkedFiles(BibEntry entry) { - final List dirs = databaseContext.getFileDirectoriesAsPaths(Globals.prefs.getFileDirectoryPreferences()); - final List extensions = ExternalFileTypes.getInstance().getExternalFileTypeSelection().stream().map(ExternalFileType::getExtension).collect(Collectors.toList()); + List result = new ArrayList<>(); - // Run the search operation: - FileFinder fileFinder = FileFinders.constructFromConfiguration(Globals.prefs.getAutoLinkPreferences()); - List newFiles = fileFinder.findAssociatedFiles(entry, dirs, extensions); + AutoSetFileLinksUtil util = new AutoSetFileLinksUtil(); + List linkedFiles = util.findassociatedNotLinkedFiles(entry, databaseContext, Globals.prefs.getFileDirectoryPreferences(), Globals.prefs.getAutoLinkPreferences(), ExternalFileTypes.getInstance()); - List result = new ArrayList<>(); - for (Path newFile : newFiles) { - boolean alreadyLinked = files.get().stream() - .map(file -> file.findIn(dirs)) - .anyMatch(file -> file.isPresent() && file.get().equals(newFile)); - if (!alreadyLinked) { - LinkedFileViewModel newLinkedFile = new LinkedFileViewModel(fromFile(newFile, dirs), entry, databaseContext); - newLinkedFile.markAsAutomaticallyFound(); - result.add(newLinkedFile); - } + for (LinkedFile linkedFile : linkedFiles) { + LinkedFileViewModel newLinkedFile = new LinkedFileViewModel(linkedFile, entry, databaseContext); + newLinkedFile.markAsAutomaticallyFound(); + result.add(newLinkedFile); } + return result; } diff --git a/src/test/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtilTest.java b/src/test/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtilTest.java new file mode 100644 index 00000000000..8bf55431ced --- /dev/null +++ b/src/test/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtilTest.java @@ -0,0 +1,55 @@ +package org.jabref.gui.externalfiles; + +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.TreeSet; + +import org.jabref.gui.externalfiletype.ExternalFileTypes; +import org.jabref.logic.util.io.AutoLinkPreferences; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.LinkedFile; +import org.jabref.model.metadata.FileDirectoryPreferences; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class AutoSetFileLinksUtilTest { + + private final FileDirectoryPreferences fileDirPrefs = mock(FileDirectoryPreferences.class); + private final AutoLinkPreferences autoLinkPrefs = new AutoLinkPreferences(false, "", true, ';'); + private final BibDatabaseContext databaseContext = mock(BibDatabaseContext.class); + private final ExternalFileTypes externalFileTypes = mock(ExternalFileTypes.class); + private final BibEntry entry = new BibEntry("article"); + private Path file; + + @Rule public TemporaryFolder folder = new TemporaryFolder(); + + @Before + public void setUp() throws Exception { + entry.setCiteKey("CiteKey"); + file = folder.newFile("CiteKey.pdf").toPath(); + when(databaseContext.getFileDirectoriesAsPaths(any())).thenReturn(Collections.singletonList(folder.getRoot().toPath())); + when(externalFileTypes.getExternalFileTypeSelection()).thenReturn(new TreeSet<>(externalFileTypes.getDefaultExternalFileTypes())); + + } + + @Test + public void test() { + //Due to mocking the externalFileType class, the file extension will not be found + List expected = Collections.singletonList(new LinkedFile("", file.toString(), "")); + + AutoSetFileLinksUtil util = new AutoSetFileLinksUtil(); + List actual = util.findassociatedNotLinkedFiles(entry, databaseContext, fileDirPrefs, autoLinkPrefs, externalFileTypes); + assertEquals(expected, actual); + } + +} From 42ea6bc9ca95caa17cbafe4cb057b5f9cce82413 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Tue, 31 Oct 2017 20:41:06 +0800 Subject: [PATCH 13/22] Update entry for every change in the source panel (#3366) * Update entry for every change in the source panel * Fix exception thrown when entry is updated while source code view is open * Fix language --- .../jabref/gui/entryeditor/EntryEditor.java | 8 -- .../org/jabref/gui/entryeditor/SourceTab.java | 119 +++++++----------- .../org/jabref/gui/util/BindingsHelper.java | 62 +++++++++ .../java/org/jabref/model/entry/BibEntry.java | 4 + src/main/resources/l10n/JabRef_da.properties | 2 - src/main/resources/l10n/JabRef_de.properties | 2 - src/main/resources/l10n/JabRef_el.properties | 2 - src/main/resources/l10n/JabRef_en.properties | 2 - src/main/resources/l10n/JabRef_es.properties | 2 - src/main/resources/l10n/JabRef_fa.properties | 2 - src/main/resources/l10n/JabRef_fr.properties | 2 - src/main/resources/l10n/JabRef_in.properties | 2 - src/main/resources/l10n/JabRef_it.properties | 2 - src/main/resources/l10n/JabRef_ja.properties | 2 - src/main/resources/l10n/JabRef_nl.properties | 2 - src/main/resources/l10n/JabRef_no.properties | 2 - .../resources/l10n/JabRef_pt_BR.properties | 2 - src/main/resources/l10n/JabRef_ru.properties | 2 - src/main/resources/l10n/JabRef_sv.properties | 2 - src/main/resources/l10n/JabRef_tr.properties | 2 - src/main/resources/l10n/JabRef_vi.properties | 2 - src/main/resources/l10n/JabRef_zh.properties | 2 - 22 files changed, 111 insertions(+), 118 deletions(-) diff --git a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java index b663ace6e7f..9a02d5adeca 100644 --- a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java +++ b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java @@ -83,7 +83,6 @@ import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.EntryType; -import org.jabref.model.entry.event.EntryChangedEvent; import org.jabref.model.entry.event.FieldAddedOrRemovedEvent; import org.jabref.preferences.JabRefPreferences; @@ -196,7 +195,6 @@ public EntryEditor(BasePanel panel) { public void setEntry(BibEntry entry) { this.entry = Objects.requireNonNull(entry); - entry.registerListener(this); entryType = EntryTypes.getTypeOrDefault(entry.getType(), this.frame.getCurrentBasePanel().getBibDatabaseContext().getMode()); @@ -223,11 +221,6 @@ public synchronized void listen(FieldAddedOrRemovedEvent event) { recalculateVisibleTabs(); } - @Subscribe - public synchronized void listen(EntryChangedEvent event) { - DefaultTaskExecutor.runInJavaFXThread(() -> sourceTab.updateSourcePane(entry)); - } - /** * Set-up key bindings specific for the entry editor. */ @@ -520,7 +513,6 @@ public void setMovingToDifferentEntry() { } private void unregisterListeners() { - this.entry.unregisterListener(this); removeSearchListeners(); } diff --git a/src/main/java/org/jabref/gui/entryeditor/SourceTab.java b/src/main/java/org/jabref/gui/entryeditor/SourceTab.java index 0cf7dd39f53..edbb62dcd57 100644 --- a/src/main/java/org/jabref/gui/entryeditor/SourceTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/SourceTab.java @@ -8,17 +8,20 @@ import javax.swing.undo.UndoManager; -import javafx.scene.Node; +import javafx.application.Platform; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.ListChangeListener; import javafx.scene.control.Tooltip; import org.jabref.Globals; import org.jabref.gui.BasePanel; -import org.jabref.gui.DialogService; -import org.jabref.gui.FXDialogService; import org.jabref.gui.IconTheme; import org.jabref.gui.undo.NamedCompound; import org.jabref.gui.undo.UndoableChangeType; import org.jabref.gui.undo.UndoableFieldChange; +import org.jabref.gui.util.BindingsHelper; +import org.jabref.gui.util.DefaultTaskExecutor; import org.jabref.logic.bibtex.BibEntryWriter; import org.jabref.logic.bibtex.InvalidFieldValueException; import org.jabref.logic.bibtex.LatexFieldFormatter; @@ -30,9 +33,11 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.InternalBibtexFields; +import de.saxsys.mvvmfx.utils.validation.ObservableRuleBasedValidator; +import de.saxsys.mvvmfx.utils.validation.ValidationMessage; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.fxmisc.easybind.EasyBind; +import org.controlsfx.control.NotificationPane; import org.fxmisc.flowless.VirtualizedScrollPane; import org.fxmisc.richtext.CodeArea; @@ -40,13 +45,12 @@ public class SourceTab extends EntryEditorTab { private static final Log LOGGER = LogFactory.getLog(SourceTab.class); private final BibDatabaseMode mode; - private final BasePanel panel; - private CodeArea codeArea; private UndoManager undoManager; + private final ObjectProperty sourceIsValid = new SimpleObjectProperty<>(); + private final ObservableRuleBasedValidator sourceValidator = new ObservableRuleBasedValidator(sourceIsValid); public SourceTab(BasePanel panel) { this.mode = panel.getBibDatabaseContext().getMode(); - this.panel = panel; this.setText(Localization.lang("%0 source", mode.getFormattedName())); this.setTooltip(new Tooltip(Localization.lang("Show/edit %0 source", mode.getFormattedName()))); this.setGraphic(IconTheme.JabRefIcon.SOURCE.getGraphicNode()); @@ -62,50 +66,12 @@ private static String getSourceString(BibEntry entry, BibDatabaseMode type) thro return stringWriter.getBuffer().toString(); } - public void updateSourcePane(BibEntry entry) { - if (codeArea != null) { - try { - codeArea.clear(); - codeArea.appendText(getSourceString(entry, mode)); - } catch (IOException ex) { - codeArea.appendText(ex.getMessage() + "\n\n" + - Localization.lang("Correct the entry, and reopen editor to display/edit source.")); - codeArea.setEditable(false); - LOGGER.debug("Incorrect entry", ex); - } - } - } - - private Node createSourceEditor(BibDatabaseMode mode) { - codeArea = new CodeArea(); + private CodeArea createSourceEditor() { + CodeArea codeArea = new CodeArea(); codeArea.setWrapText(true); codeArea.lookup(".styled-text-area").setStyle( "-fx-font-size: " + Globals.prefs.getFontSizeFX() + "pt;"); - // store source if new tab is selected (if this one is not focused anymore) - EasyBind.subscribe(codeArea.focusedProperty(), focused -> { - if (!focused) { - storeSource(); - } - }); - - try { - String srcString = getSourceString(this.currentEntry, mode); - codeArea.appendText(srcString); - } catch (IOException ex) { - codeArea.appendText(ex.getMessage() + "\n\n" + - Localization.lang("Correct the entry, and reopen editor to display/edit source.")); - codeArea.setEditable(false); - LOGGER.debug("Incorrect entry", ex); - } - - // set the database to dirty when something is changed in the source tab - EasyBind.subscribe(codeArea.beingUpdatedProperty(), updated -> { - if (updated) { - panel.markBaseChanged(); - } - }); - - return new VirtualizedScrollPane<>(codeArea); + return codeArea; } @Override @@ -115,17 +81,42 @@ public boolean shouldShow(BibEntry entry) { @Override protected void bindToEntry(BibEntry entry) { - this.setContent(createSourceEditor(mode)); + CodeArea codeArea = createSourceEditor(); + VirtualizedScrollPane node = new VirtualizedScrollPane<>(codeArea); + NotificationPane notificationPane = new NotificationPane(node); + notificationPane.setShowFromTop(false); + sourceValidator.getValidationStatus().getMessages().addListener((ListChangeListener) c -> { + while (c.next()) { + Platform.runLater(() -> sourceValidator.getValidationStatus().getHighestMessage().ifPresent(validationMessage -> notificationPane.show(validationMessage.getMessage()))); + } + }); + this.setContent(notificationPane); + + // Store source for every change in the source code + // and update source code for every change of entry field values + BindingsHelper.bindContentBidirectional(entry.getFieldsObservable(), codeArea.textProperty(), this::storeSource, fields -> { + DefaultTaskExecutor.runInJavaFXThread(() -> { + codeArea.clear(); + try { + codeArea.appendText(getSourceString(entry, mode)); + } catch (IOException ex) { + codeArea.setEditable(false); + codeArea.appendText(ex.getMessage() + "\n\n" + + Localization.lang("Correct the entry, and reopen editor to display/edit source.")); + LOGGER.debug("Incorrect entry", ex); + } + }); + }); } - private void storeSource() { - if (codeArea.getText().isEmpty()) { + private void storeSource(String text) { + if (text.isEmpty()) { return; } BibtexParser bibtexParser = new BibtexParser(Globals.prefs.getImportFormatPreferences()); try { - ParserResult parserResult = bibtexParser.parse(new StringReader(codeArea.getText())); + ParserResult parserResult = bibtexParser.parse(new StringReader(text)); BibDatabase database = parserResult.getDatabase(); if (database.getEntryCount() > 1) { @@ -185,29 +176,9 @@ private void storeSource() { } compound.end(); undoManager.addEdit(compound); - - } catch (InvalidFieldValueException | IOException ex) { - // The source couldn't be parsed, so the user is given an - // error message, and the choice to keep or revert the contents - // of the source text field. - + } catch (InvalidFieldValueException | IllegalStateException | IOException ex) { + sourceIsValid.setValue(ValidationMessage.error(Localization.lang("Problem with parsing entry") + ": " + ex.getMessage())); LOGGER.debug("Incorrect source", ex); - DialogService dialogService = new FXDialogService(); - boolean keepEditing = dialogService.showConfirmationDialogAndWait( - Localization.lang("Problem with parsing entry"), - Localization.lang("Error") + ": " + ex.getMessage(), - Localization.lang("Edit"), - Localization.lang("Revert to original source") - ); - - if (!keepEditing) { - // Revert - try { - codeArea.replaceText(0, codeArea.getText().length(), getSourceString(this.currentEntry, mode)); - } catch (IOException e) { - LOGGER.debug("Incorrect source", e); - } - } } } } diff --git a/src/main/java/org/jabref/gui/util/BindingsHelper.java b/src/main/java/org/jabref/gui/util/BindingsHelper.java index 246ad43ff50..22b26f2db82 100644 --- a/src/main/java/org/jabref/gui/util/BindingsHelper.java +++ b/src/main/java/org/jabref/gui/util/BindingsHelper.java @@ -1,6 +1,7 @@ package org.jabref.gui.util; import java.util.List; +import java.util.Map; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; @@ -14,7 +15,9 @@ import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.collections.ListChangeListener; +import javafx.collections.MapChangeListener; import javafx.collections.ObservableList; +import javafx.collections.ObservableMap; import javafx.css.PseudoClass; import javafx.scene.Node; @@ -128,6 +131,25 @@ public static void bindContentBidirectional(ListProperty listProperty, updateB); } + public static void bindContentBidirectional(ObservableMap propertyA, ObservableValue propertyB, Consumer updateA, Consumer> updateB) { + final BidirectionalMapBinding binding = new BidirectionalMapBinding<>(propertyA, propertyB, updateA, updateB); + + // use list as initial source + updateB.accept(propertyA); + + propertyA.addListener(binding); + propertyB.addListener(binding); + } + + public static void bindContentBidirectional(ObservableMap propertyA, Property propertyB, Consumer updateA, Function, B> mapToB) { + Consumer> updateB = newValueList -> propertyB.setValue(mapToB.apply(newValueList)); + bindContentBidirectional( + propertyA, + propertyB, + updateA, + updateB); + } + private static class BidirectionalBinding { private final ObservableValue propertyA; @@ -208,4 +230,44 @@ public void onChanged(Change c) { } } } + + private static class BidirectionalMapBinding implements MapChangeListener, ChangeListener { + + private final ObservableMap mapProperty; + private final ObservableValue property; + private final Consumer updateA; + private final Consumer> updateB; + private boolean updating = false; + + public BidirectionalMapBinding(ObservableMap mapProperty, ObservableValue property, Consumer updateA, Consumer> updateB) { + this.mapProperty = mapProperty; + this.property = property; + this.updateA = updateA; + this.updateB = updateB; + } + + @Override + public void changed(ObservableValue observable, B oldValue, B newValue) { + if (!updating) { + try { + updating = true; + updateA.accept(newValue); + } finally { + updating = false; + } + } + } + + @Override + public void onChanged(Change c) { + if (!updating) { + try { + updating = true; + updateB.accept(mapProperty); + } finally { + updating = false; + } + } + } + } } diff --git a/src/main/java/org/jabref/model/entry/BibEntry.java b/src/main/java/org/jabref/model/entry/BibEntry.java index d383b09b5c9..db5bba9469c 100644 --- a/src/main/java/org/jabref/model/entry/BibEntry.java +++ b/src/main/java/org/jabref/model/entry/BibEntry.java @@ -843,6 +843,10 @@ public Optional addFile(LinkedFile file) { return setFiles(linkedFiles); } + public ObservableMap getFieldsObservable() { + return fields; + } + private interface GetFieldInterface { Optional getValueForField(String fieldName); diff --git a/src/main/resources/l10n/JabRef_da.properties b/src/main/resources/l10n/JabRef_da.properties index 65cd98df5d2..ea2a4edf45c 100644 --- a/src/main/resources/l10n/JabRef_da.properties +++ b/src/main/resources/l10n/JabRef_da.properties @@ -1025,8 +1025,6 @@ Resolve_strings_for_standard_BibTeX_fields_only=Slå_kun_strenge_op_for_standard resolved=løst -Revert_to_original_source=Ret_tilbage_til_oprindelig_kildekode - Review=Kommentarer Review_changes=Gennemse_ændringer diff --git a/src/main/resources/l10n/JabRef_de.properties b/src/main/resources/l10n/JabRef_de.properties index 83dea0b6cfb..ccfa3850681 100644 --- a/src/main/resources/l10n/JabRef_de.properties +++ b/src/main/resources/l10n/JabRef_de.properties @@ -1025,8 +1025,6 @@ Resolve_strings_for_standard_BibTeX_fields_only=Strings_nur_für_Standard-BibTeX resolved=davon_aufgelöst -Revert_to_original_source=Original_wiederherstellen - Review=Überprüfung Review_changes=Änderungen_überprüfen diff --git a/src/main/resources/l10n/JabRef_el.properties b/src/main/resources/l10n/JabRef_el.properties index 05f875c196a..679dbe3cb43 100644 --- a/src/main/resources/l10n/JabRef_el.properties +++ b/src/main/resources/l10n/JabRef_el.properties @@ -1025,8 +1025,6 @@ Resolve_strings_for_standard_BibTeX_fields_only= resolved= -Revert_to_original_source= - Review= Review_changes= diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 87abfe1ce71..f9bc1b5fb4e 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -1025,8 +1025,6 @@ Resolve_strings_for_standard_BibTeX_fields_only=Resolve_strings_for_standard_Bib resolved=resolved -Revert_to_original_source=Revert_to_original_source - Review=Review Review_changes=Review_changes diff --git a/src/main/resources/l10n/JabRef_es.properties b/src/main/resources/l10n/JabRef_es.properties index 542112d1651..3907820c9ce 100644 --- a/src/main/resources/l10n/JabRef_es.properties +++ b/src/main/resources/l10n/JabRef_es.properties @@ -1025,8 +1025,6 @@ Resolve_strings_for_standard_BibTeX_fields_only=Resolver_cadenas_únicamente_par resolved=resuelto -Revert_to_original_source=Volver_a_la_fuente_original - Review=Revisar Review_changes=Revisar_cambios diff --git a/src/main/resources/l10n/JabRef_fa.properties b/src/main/resources/l10n/JabRef_fa.properties index 24dd3b29d54..ce453356635 100644 --- a/src/main/resources/l10n/JabRef_fa.properties +++ b/src/main/resources/l10n/JabRef_fa.properties @@ -1025,8 +1025,6 @@ Resolve_strings_for_standard_BibTeX_fields_only= resolved= -Revert_to_original_source= - Review= Review_changes= diff --git a/src/main/resources/l10n/JabRef_fr.properties b/src/main/resources/l10n/JabRef_fr.properties index d190a6b8fd5..9e30d0fdf7f 100644 --- a/src/main/resources/l10n/JabRef_fr.properties +++ b/src/main/resources/l10n/JabRef_fr.properties @@ -1025,8 +1025,6 @@ Resolve_strings_for_standard_BibTeX_fields_only=Traiter_les_chaînes_pour_les_ch resolved=résolu -Revert_to_original_source=Rétablir_le_contenu_initial - Review=Remarques Review_changes=Revoir_les_changements diff --git a/src/main/resources/l10n/JabRef_in.properties b/src/main/resources/l10n/JabRef_in.properties index 9e3c200840a..622362c0a07 100644 --- a/src/main/resources/l10n/JabRef_in.properties +++ b/src/main/resources/l10n/JabRef_in.properties @@ -1025,8 +1025,6 @@ Resolve_strings_for_standard_BibTeX_fields_only=Selesaikan_masalah_string_hanya_ resolved=sudah_diselesaikan -Revert_to_original_source=Kembalikan_ke_sumber_asli - Review=Periksa_ulang Review_changes=Periksa_ulang_perubahan diff --git a/src/main/resources/l10n/JabRef_it.properties b/src/main/resources/l10n/JabRef_it.properties index 737394f837b..6deab86e64f 100644 --- a/src/main/resources/l10n/JabRef_it.properties +++ b/src/main/resources/l10n/JabRef_it.properties @@ -1025,8 +1025,6 @@ Resolve_strings_for_standard_BibTeX_fields_only=Risolve_le_stringhe_solo_per_i_c resolved=risolto -Revert_to_original_source=Ripristina_il_contenuto_iniziale - Review=Rivedi Review_changes=Rivedi_le_modifiche diff --git a/src/main/resources/l10n/JabRef_ja.properties b/src/main/resources/l10n/JabRef_ja.properties index 1064e1fcf0a..6cc96198a66 100644 --- a/src/main/resources/l10n/JabRef_ja.properties +++ b/src/main/resources/l10n/JabRef_ja.properties @@ -1025,8 +1025,6 @@ Resolve_strings_for_standard_BibTeX_fields_only=文字列をBibTeX標準フィ resolved=解消しました -Revert_to_original_source=元のソースに復帰する - Review=論評 Review_changes=変更を検査する diff --git a/src/main/resources/l10n/JabRef_nl.properties b/src/main/resources/l10n/JabRef_nl.properties index b16c2f31a2e..8caf09ab157 100644 --- a/src/main/resources/l10n/JabRef_nl.properties +++ b/src/main/resources/l10n/JabRef_nl.properties @@ -1025,8 +1025,6 @@ Resolve_strings_for_standard_BibTeX_fields_only= resolved=opgelost -Revert_to_original_source=Herstel_naar_originele_bron - Review=Recensie Review_changes=Bekijk_veranderingen diff --git a/src/main/resources/l10n/JabRef_no.properties b/src/main/resources/l10n/JabRef_no.properties index 1b8348287ae..57668394554 100644 --- a/src/main/resources/l10n/JabRef_no.properties +++ b/src/main/resources/l10n/JabRef_no.properties @@ -1025,8 +1025,6 @@ Resolve_strings_for_standard_BibTeX_fields_only=Sl\u00e5_opp_strenger_kun_for_st resolved=tatt_h\u00e5nd_om -Revert_to_original_source=Resett_til_opprinnelig_kildekode - Review=Kommentarer Review_changes=Se_over_endringer diff --git a/src/main/resources/l10n/JabRef_pt_BR.properties b/src/main/resources/l10n/JabRef_pt_BR.properties index afc2cf1cc07..fe0c865c65d 100644 --- a/src/main/resources/l10n/JabRef_pt_BR.properties +++ b/src/main/resources/l10n/JabRef_pt_BR.properties @@ -1025,8 +1025,6 @@ Resolve_strings_for_standard_BibTeX_fields_only=Resolver_strings_apenas_para_cam resolved=resolvido -Revert_to_original_source=Reverter_para_o_original - Review=Revisar Review_changes=Revisar_mudanças diff --git a/src/main/resources/l10n/JabRef_ru.properties b/src/main/resources/l10n/JabRef_ru.properties index a26fdb69084..327a2a8a5c5 100644 --- a/src/main/resources/l10n/JabRef_ru.properties +++ b/src/main/resources/l10n/JabRef_ru.properties @@ -1025,8 +1025,6 @@ Resolve_strings_for_standard_BibTeX_fields_only=Разрешение_для_ст resolved=разрешено -Revert_to_original_source=Восстановить_исходный_источник - Review=Просмотр Review_changes=Просмотр_изменений diff --git a/src/main/resources/l10n/JabRef_sv.properties b/src/main/resources/l10n/JabRef_sv.properties index 5c457860186..af8832d88b2 100644 --- a/src/main/resources/l10n/JabRef_sv.properties +++ b/src/main/resources/l10n/JabRef_sv.properties @@ -1025,8 +1025,6 @@ Resolve_strings_for_standard_BibTeX_fields_only=Ersätt_bara_strängar_för_de_v resolved= -Revert_to_original_source= - Review= Review_changes=Kontrollera_ändringar diff --git a/src/main/resources/l10n/JabRef_tr.properties b/src/main/resources/l10n/JabRef_tr.properties index 985f5187150..cdca5d0efda 100644 --- a/src/main/resources/l10n/JabRef_tr.properties +++ b/src/main/resources/l10n/JabRef_tr.properties @@ -1025,8 +1025,6 @@ Resolve_strings_for_standard_BibTeX_fields_only=Yalnızca_standart_BibTeX_alan_d resolved=çözümlendi -Revert_to_original_source=Orijinal_kaynağa_döndür - Review=Gözden_geçir Review_changes=Değişklikleri_incele diff --git a/src/main/resources/l10n/JabRef_vi.properties b/src/main/resources/l10n/JabRef_vi.properties index 79b95120b63..41d582e3299 100644 --- a/src/main/resources/l10n/JabRef_vi.properties +++ b/src/main/resources/l10n/JabRef_vi.properties @@ -1025,8 +1025,6 @@ Resolve_strings_for_standard_BibTeX_fields_only=Chỉ_giải_các_chuỗi_cho_c resolved=được_giải -Revert_to_original_source=Trả_ngược_lại_nguồn_ban_đầu - Review=Xem_xét_lại Review_changes=Xem_xét_lại_các_thay_đổi diff --git a/src/main/resources/l10n/JabRef_zh.properties b/src/main/resources/l10n/JabRef_zh.properties index 8d56d18cd9e..3085049edd0 100644 --- a/src/main/resources/l10n/JabRef_zh.properties +++ b/src/main/resources/l10n/JabRef_zh.properties @@ -1025,8 +1025,6 @@ Resolve_strings_for_standard_BibTeX_fields_only=只处理标准_BibTeX_域的简 resolved=已解决 -Revert_to_original_source=恢复到初始源 - Review=评论 Review_changes=复查修改 From 1ebdea452d5ea07f933b90b55b06df99ee57e13f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Lenhard?= Date: Tue, 31 Oct 2017 19:59:46 +0100 Subject: [PATCH 14/22] Titles are optional at crossref (#3378) --- CHANGELOG.md | 1 + src/main/java/org/jabref/logic/importer/fetcher/CrossRef.java | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf91c2e88ff..cefce0a17d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `# - We fixed an issue where JabRef would not show the translated content at some points, although there existed a translation - We fixed an issue where editing in the source tab would override content of other entries [#3352](https://github.com/JabRef/jabref/issues/3352#issue-268580818) - We fixed several issues with the automatic linking of files in the entry editor where files were not found or not correctly saved in the bibtex source [#3346](https://github.com/JabRef/jabref/issues/3346) + - We fixed an issue where fetching entries from crossref that had no titles caused an error [#3376](https://github.com/JabRef/jabref/issues/3376) ### Removed diff --git a/src/main/java/org/jabref/logic/importer/fetcher/CrossRef.java b/src/main/java/org/jabref/logic/importer/fetcher/CrossRef.java index 3bbc0705db2..384c6b9986e 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/CrossRef.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/CrossRef.java @@ -110,7 +110,9 @@ private BibEntry jsonItemToBibEntry(JSONObject item) throws ParseException { try { BibEntry entry = new BibEntry(); entry.setType(convertType(item.getString("type"))); - entry.setField(FieldName.TITLE, item.getJSONArray("title").optString(0)); + entry.setField(FieldName.TITLE, + Optional.ofNullable(item.optJSONArray("title")) + .map(array -> array.optString(0)).orElse("")); entry.setField(FieldName.SUBTITLE, Optional.ofNullable(item.optJSONArray("subtitle")) .map(array -> array.optString(0)).orElse("")); From 7e88ac2489745a1ea436b586e1d580e1754730e1 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Wed, 1 Nov 2017 13:04:31 +0800 Subject: [PATCH 15/22] Source tab: hide notification pane when code is correct again (#3387) --- src/main/java/org/jabref/gui/entryeditor/SourceTab.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/jabref/gui/entryeditor/SourceTab.java b/src/main/java/org/jabref/gui/entryeditor/SourceTab.java index edbb62dcd57..07c28472823 100644 --- a/src/main/java/org/jabref/gui/entryeditor/SourceTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/SourceTab.java @@ -8,7 +8,6 @@ import javax.swing.undo.UndoManager; -import javafx.application.Platform; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.ListChangeListener; @@ -86,8 +85,10 @@ protected void bindToEntry(BibEntry entry) { NotificationPane notificationPane = new NotificationPane(node); notificationPane.setShowFromTop(false); sourceValidator.getValidationStatus().getMessages().addListener((ListChangeListener) c -> { - while (c.next()) { - Platform.runLater(() -> sourceValidator.getValidationStatus().getHighestMessage().ifPresent(validationMessage -> notificationPane.show(validationMessage.getMessage()))); + if (sourceValidator.getValidationStatus().isValid()) { + notificationPane.hide(); + } else { + sourceValidator.getValidationStatus().getHighestMessage().ifPresent(validationMessage -> notificationPane.show(validationMessage.getMessage())); } }); this.setContent(notificationPane); @@ -176,6 +177,8 @@ private void storeSource(String text) { } compound.end(); undoManager.addEdit(compound); + + sourceIsValid.setValue(null); } catch (InvalidFieldValueException | IllegalStateException | IOException ex) { sourceIsValid.setValue(ValidationMessage.error(Localization.lang("Problem with parsing entry") + ": " + ex.getMessage())); LOGGER.debug("Incorrect source", ex); From 84e90719a8afd7f986d7f67dbfb7255f596a93dc Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Wed, 1 Nov 2017 17:36:51 +0800 Subject: [PATCH 16/22] Update richtext and flowless (#3386) --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 3af1da6f257..cbf49a61f77 100644 --- a/build.gradle +++ b/build.gradle @@ -105,8 +105,8 @@ dependencies { compile 'de.jensd:fontawesomefx-materialdesignfont:1.7.22-4' compile 'de.saxsys:mvvmfx-validation:1.7.0' compile 'org.fxmisc.easybind:easybind:1.0.3' - compile 'org.fxmisc.flowless:flowless:0.5.2' - compile 'org.fxmisc.richtext:richtextfx:0.7-M5' + compile 'org.fxmisc.flowless:flowless:0.6' + compile 'org.fxmisc.richtext:richtextfx:0.8.1' // Cannot be updated to 9.*.* until Jabref works with Java 9 compile 'org.controlsfx:controlsfx:8.40.14' From f6efc9f402aba53eeb3b2e5d54a739f8a28eca71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erdem=20Dereba=C5=9Fo=C4=9Flu?= Date: Wed, 1 Nov 2017 17:16:05 +0200 Subject: [PATCH 17/22] Added checking integrity dialog (#3388) * Added checking integrity dialog. * Added localization, fixed styling. --- CHANGELOG.md | 2 +- .../gui/actions/IntegrityCheckAction.java | 36 ++++++++++++++++++- src/main/resources/l10n/JabRef_da.properties | 1 + src/main/resources/l10n/JabRef_de.properties | 1 + src/main/resources/l10n/JabRef_el.properties | 1 + src/main/resources/l10n/JabRef_en.properties | 1 + src/main/resources/l10n/JabRef_es.properties | 1 + src/main/resources/l10n/JabRef_fa.properties | 1 + src/main/resources/l10n/JabRef_fr.properties | 1 + src/main/resources/l10n/JabRef_in.properties | 1 + src/main/resources/l10n/JabRef_it.properties | 1 + src/main/resources/l10n/JabRef_ja.properties | 1 + src/main/resources/l10n/JabRef_nl.properties | 1 + src/main/resources/l10n/JabRef_no.properties | 1 + .../resources/l10n/JabRef_pt_BR.properties | 1 + src/main/resources/l10n/JabRef_ru.properties | 1 + src/main/resources/l10n/JabRef_sv.properties | 1 + src/main/resources/l10n/JabRef_tr.properties | 1 + src/main/resources/l10n/JabRef_vi.properties | 1 + src/main/resources/l10n/JabRef_zh.properties | 1 + 20 files changed, 54 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cefce0a17d3..216f723d1f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,7 +25,7 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `# - We no longer create a new entry editor when selecting a new entry to increase performance. [#3187](https://github.com/JabRef/jabref/pull/3187) - We increased performance and decreased the memory footprint of the entry editor drastically. [#3331](https://github.com/JabRef/jabref/pull/3331) - Late initialization of the context menus in the entry editor. This improves performance and memory footprint further [#3340](https://github.com/JabRef/jabref/pull/3340) - +- We added a dialog to show that JabRef is working on checking integrity. [#3358](https://github.com/JabRef/jabref/issues/3358) ### Fixed - We fixed the translation of \textendash in the entry preview [#3307](https://github.com/JabRef/jabref/issues/3307) diff --git a/src/main/java/org/jabref/gui/actions/IntegrityCheckAction.java b/src/main/java/org/jabref/gui/actions/IntegrityCheckAction.java index d458fc3b692..be865ab02a7 100644 --- a/src/main/java/org/jabref/gui/actions/IntegrityCheckAction.java +++ b/src/main/java/org/jabref/gui/actions/IntegrityCheckAction.java @@ -12,10 +12,12 @@ import javax.swing.JDialog; import javax.swing.JOptionPane; import javax.swing.JPopupMenu; +import javax.swing.JProgressBar; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.ListSelectionModel; import javax.swing.RowFilter; +import javax.swing.SwingWorker; import javax.swing.table.AbstractTableModel; import javax.swing.table.TableColumnModel; import javax.swing.table.TableModel; @@ -30,9 +32,12 @@ import com.jgoodies.forms.builder.FormBuilder; import com.jgoodies.forms.layout.FormLayout; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; public class IntegrityCheckAction extends MnemonicAwareAction { + private static final Log LOGGER = LogFactory.getLog(IntegrityCheckAction.class); private static final String ELLIPSES = "..."; private final JabRefFrame frame; @@ -50,7 +55,36 @@ public void actionPerformed(ActionEvent e) { Globals.prefs.getBibtexKeyPatternPreferences(), Globals.journalAbbreviationLoader .getRepository(Globals.prefs.getJournalAbbreviationPreferences())); - List messages = check.checkBibtexDatabase(); + + final JDialog integrityDialog = new JDialog(frame, true); + integrityDialog.setUndecorated(true); + integrityDialog.setLocationRelativeTo(frame); + JProgressBar integrityProgressBar = new JProgressBar(); + integrityProgressBar.setIndeterminate(true); + integrityProgressBar.setStringPainted(true); + integrityProgressBar.setString(Localization.lang("Checking integrity...")); + integrityDialog.add(integrityProgressBar); + integrityDialog.pack(); + SwingWorker, Void> worker = new SwingWorker, Void>() { + @Override + protected List doInBackground() { + List messages = check.checkBibtexDatabase(); + return messages; + } + + @Override + protected void done() { + integrityDialog.dispose(); + } + }; + worker.execute(); + integrityDialog.setVisible(true); + List messages = null; + try { + messages = worker.get(); + } catch (Exception ex) { + LOGGER.error("Integrity check failed.", ex); + } if (messages.isEmpty()) { JOptionPane.showMessageDialog(frame.getCurrentBasePanel(), Localization.lang("No problems found.")); diff --git a/src/main/resources/l10n/JabRef_da.properties b/src/main/resources/l10n/JabRef_da.properties index ea2a4edf45c..b38abf85f37 100644 --- a/src/main/resources/l10n/JabRef_da.properties +++ b/src/main/resources/l10n/JabRef_da.properties @@ -2339,3 +2339,4 @@ Show_console_output_(only_necessary_when_the_launcher_is_used)= Remove_line_breaks= Removes_all_line_breaks_in_the_field_content.= +Checking_integrity...= diff --git a/src/main/resources/l10n/JabRef_de.properties b/src/main/resources/l10n/JabRef_de.properties index ccfa3850681..773ccdccc7b 100644 --- a/src/main/resources/l10n/JabRef_de.properties +++ b/src/main/resources/l10n/JabRef_de.properties @@ -2339,3 +2339,4 @@ Show_console_output_(only_necessary_when_the_launcher_is_used)=Anzeigen_der_Kons Remove_line_breaks=Entfernen_der_Zeilenumbrüche Removes_all_line_breaks_in_the_field_content.=Entfernen_aller_Zeilenumbrüche_im_Inhalt_des_Feldes. +Checking_integrity...= diff --git a/src/main/resources/l10n/JabRef_el.properties b/src/main/resources/l10n/JabRef_el.properties index 679dbe3cb43..150eaf23d9c 100644 --- a/src/main/resources/l10n/JabRef_el.properties +++ b/src/main/resources/l10n/JabRef_el.properties @@ -2339,3 +2339,4 @@ Show_console_output_(only_necessary_when_the_launcher_is_used)= Remove_line_breaks= Removes_all_line_breaks_in_the_field_content.= +Checking_integrity...= diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index f9bc1b5fb4e..49f679c064a 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -2339,3 +2339,4 @@ Show_console_output_(only_necessary_when_the_launcher_is_used)=Show_console_outp Remove_line_breaks=Remove_line_breaks Removes_all_line_breaks_in_the_field_content.=Removes_all_line_breaks_in_the_field_content. +Checking_integrity...=Checking_integrity... diff --git a/src/main/resources/l10n/JabRef_es.properties b/src/main/resources/l10n/JabRef_es.properties index 3907820c9ce..3c6814b1a9e 100644 --- a/src/main/resources/l10n/JabRef_es.properties +++ b/src/main/resources/l10n/JabRef_es.properties @@ -2339,3 +2339,4 @@ Show_console_output_(only_necessary_when_the_launcher_is_used)= Remove_line_breaks= Removes_all_line_breaks_in_the_field_content.= +Checking_integrity...= diff --git a/src/main/resources/l10n/JabRef_fa.properties b/src/main/resources/l10n/JabRef_fa.properties index ce453356635..86a38e5b83b 100644 --- a/src/main/resources/l10n/JabRef_fa.properties +++ b/src/main/resources/l10n/JabRef_fa.properties @@ -2339,3 +2339,4 @@ Show_console_output_(only_necessary_when_the_launcher_is_used)= Remove_line_breaks= Removes_all_line_breaks_in_the_field_content.= +Checking_integrity...= diff --git a/src/main/resources/l10n/JabRef_fr.properties b/src/main/resources/l10n/JabRef_fr.properties index 9e30d0fdf7f..856e9ade784 100644 --- a/src/main/resources/l10n/JabRef_fr.properties +++ b/src/main/resources/l10n/JabRef_fr.properties @@ -2339,3 +2339,4 @@ Show_console_output_(only_necessary_when_the_launcher_is_used)=Afficher_la_sorti Remove_line_breaks=Supprimer_les_sauts_de_ligne Removes_all_line_breaks_in_the_field_content.=Supprime_tous_les_sauts_de_ligne_du_contenu_d'un_champ +Checking_integrity...= diff --git a/src/main/resources/l10n/JabRef_in.properties b/src/main/resources/l10n/JabRef_in.properties index 622362c0a07..73a44c921f8 100644 --- a/src/main/resources/l10n/JabRef_in.properties +++ b/src/main/resources/l10n/JabRef_in.properties @@ -2339,3 +2339,4 @@ Show_console_output_(only_necessary_when_the_launcher_is_used)= Remove_line_breaks= Removes_all_line_breaks_in_the_field_content.= +Checking_integrity...= diff --git a/src/main/resources/l10n/JabRef_it.properties b/src/main/resources/l10n/JabRef_it.properties index 6deab86e64f..61325b44c38 100644 --- a/src/main/resources/l10n/JabRef_it.properties +++ b/src/main/resources/l10n/JabRef_it.properties @@ -2339,3 +2339,4 @@ Show_console_output_(only_necessary_when_the_launcher_is_used)= Remove_line_breaks= Removes_all_line_breaks_in_the_field_content.= +Checking_integrity...= diff --git a/src/main/resources/l10n/JabRef_ja.properties b/src/main/resources/l10n/JabRef_ja.properties index 6cc96198a66..ec0cc0d1346 100644 --- a/src/main/resources/l10n/JabRef_ja.properties +++ b/src/main/resources/l10n/JabRef_ja.properties @@ -2339,3 +2339,4 @@ Show_console_output_(only_necessary_when_the_launcher_is_used)= Remove_line_breaks= Removes_all_line_breaks_in_the_field_content.= +Checking_integrity...= diff --git a/src/main/resources/l10n/JabRef_nl.properties b/src/main/resources/l10n/JabRef_nl.properties index 8caf09ab157..dfcbd77d6d8 100644 --- a/src/main/resources/l10n/JabRef_nl.properties +++ b/src/main/resources/l10n/JabRef_nl.properties @@ -2339,3 +2339,4 @@ Show_console_output_(only_necessary_when_the_launcher_is_used)= Remove_line_breaks= Removes_all_line_breaks_in_the_field_content.= +Checking_integrity...= diff --git a/src/main/resources/l10n/JabRef_no.properties b/src/main/resources/l10n/JabRef_no.properties index 57668394554..5dcfcac2b5b 100644 --- a/src/main/resources/l10n/JabRef_no.properties +++ b/src/main/resources/l10n/JabRef_no.properties @@ -2339,3 +2339,4 @@ Show_console_output_(only_necessary_when_the_launcher_is_used)= Remove_line_breaks= Removes_all_line_breaks_in_the_field_content.= +Checking_integrity...= diff --git a/src/main/resources/l10n/JabRef_pt_BR.properties b/src/main/resources/l10n/JabRef_pt_BR.properties index fe0c865c65d..aaa3b456af6 100644 --- a/src/main/resources/l10n/JabRef_pt_BR.properties +++ b/src/main/resources/l10n/JabRef_pt_BR.properties @@ -2339,3 +2339,4 @@ Show_console_output_(only_necessary_when_the_launcher_is_used)= Remove_line_breaks= Removes_all_line_breaks_in_the_field_content.= +Checking_integrity...= diff --git a/src/main/resources/l10n/JabRef_ru.properties b/src/main/resources/l10n/JabRef_ru.properties index 327a2a8a5c5..14fb41637a1 100644 --- a/src/main/resources/l10n/JabRef_ru.properties +++ b/src/main/resources/l10n/JabRef_ru.properties @@ -2339,3 +2339,4 @@ Show_console_output_(only_necessary_when_the_launcher_is_used)= Remove_line_breaks= Removes_all_line_breaks_in_the_field_content.= +Checking_integrity...= diff --git a/src/main/resources/l10n/JabRef_sv.properties b/src/main/resources/l10n/JabRef_sv.properties index af8832d88b2..da91870aae7 100644 --- a/src/main/resources/l10n/JabRef_sv.properties +++ b/src/main/resources/l10n/JabRef_sv.properties @@ -2339,3 +2339,4 @@ Show_console_output_(only_necessary_when_the_launcher_is_used)= Remove_line_breaks= Removes_all_line_breaks_in_the_field_content.= +Checking_integrity...= diff --git a/src/main/resources/l10n/JabRef_tr.properties b/src/main/resources/l10n/JabRef_tr.properties index cdca5d0efda..8062bcb2344 100644 --- a/src/main/resources/l10n/JabRef_tr.properties +++ b/src/main/resources/l10n/JabRef_tr.properties @@ -2339,3 +2339,4 @@ Show_console_output_(only_necessary_when_the_launcher_is_used)=Consol_çıktıs Remove_line_breaks= Removes_all_line_breaks_in_the_field_content.= +Checking_integrity...= diff --git a/src/main/resources/l10n/JabRef_vi.properties b/src/main/resources/l10n/JabRef_vi.properties index 41d582e3299..78f0fc78a79 100644 --- a/src/main/resources/l10n/JabRef_vi.properties +++ b/src/main/resources/l10n/JabRef_vi.properties @@ -2339,3 +2339,4 @@ Show_console_output_(only_necessary_when_the_launcher_is_used)= Remove_line_breaks= Removes_all_line_breaks_in_the_field_content.= +Checking_integrity...= diff --git a/src/main/resources/l10n/JabRef_zh.properties b/src/main/resources/l10n/JabRef_zh.properties index 3085049edd0..dcc5316f433 100644 --- a/src/main/resources/l10n/JabRef_zh.properties +++ b/src/main/resources/l10n/JabRef_zh.properties @@ -2339,3 +2339,4 @@ Show_console_output_(only_necessary_when_the_launcher_is_used)= Remove_line_breaks= Removes_all_line_breaks_in_the_field_content.= +Checking_integrity...= From d5f0b695fbc5ee7895a0bd5536e11116be7599d8 Mon Sep 17 00:00:00 2001 From: Christoph Date: Wed, 1 Nov 2017 16:28:24 +0100 Subject: [PATCH 18/22] Export pdf/linked files (#3147) * First approach for implementing PDF Exporter Remove code that produces NPE * First approach for implementing PDF Exporter Remove code that produces NPE Export linked files Don't replace existings Create new Action for exporting files TODO: Add to menu Move to Tools entry try around with some background task stuf remove compile errro Add Progessbar dialog Add localization Fix checkstyle extract to inner class Add some more output messages renamed variables Fix gui display * Add to File menu and context menu Fix localization to copy files * Remove separator from tools menu * fix fileUtil and localzation from rebase * Remove empty line Avoid code duplicaiton * Move service to new class * Fix translation * Add idea for list view dialog * Add TableView to show log message * Add class for table view data * Add Green/Red icons for status Reorder columns Fix translation error * fix checkstyle and make cancel button close button remove sys out * fix codacy * Renamings * Remove setter Use Task instead of Service Use CellValueFactory * Add fxml and move to package * fix controller * add button data * Add new wrapper class for injection * Add todos hinting at empty * Fix injection of data Switch gui to border pane add tooltip to factory * rebame and fix checkstyle * Make variables private * fix merge error in lang files * Fix some coday warnings * Rename l10n Add timestamp Improve GUI * fix showing of progress bar --- CHANGELOG.md | 1 + .../org/jabref/gui/AbstractDialogView.java | 5 + .../java/org/jabref/gui/DialogService.java | 10 ++ .../java/org/jabref/gui/FXDialogService.java | 18 +++ src/main/java/org/jabref/gui/JabRefFrame.java | 5 +- .../jabref/gui/copyfiles/CopyFilesAction.java | 70 ++++++++++ .../copyfiles/CopyFilesDialogController.java | 59 +++++++++ .../gui/copyfiles/CopyFilesDialogView.java | 36 ++++++ .../copyfiles/CopyFilesDialogViewModel.java | 21 +++ .../CopyFilesResultItemViewModel.java | 42 ++++++ .../CopyFilesResultListDependency.java | 31 +++++ .../jabref/gui/copyfiles/CopyFilesTask.java | 122 ++++++++++++++++++ .../org/jabref/gui/menus/RightClickMenu.java | 2 + .../gui/util/ValueTableCellFactory.java | 18 ++- .../jabref/logic/exporter/ExportFormat.java | 6 - .../jabref/logic/exporter/IExportFormat.java | 15 --- .../jabref/logic/pdf/FileAnnotationCache.java | 2 +- .../org/jabref/logic/util/io/FileUtil.java | 3 +- src/main/resources/l10n/JabRef_da.properties | 8 ++ src/main/resources/l10n/JabRef_de.properties | 8 ++ src/main/resources/l10n/JabRef_el.properties | 8 ++ src/main/resources/l10n/JabRef_en.properties | 8 ++ src/main/resources/l10n/JabRef_es.properties | 8 ++ src/main/resources/l10n/JabRef_fa.properties | 8 ++ src/main/resources/l10n/JabRef_fr.properties | 9 ++ src/main/resources/l10n/JabRef_in.properties | 8 ++ src/main/resources/l10n/JabRef_it.properties | 8 ++ src/main/resources/l10n/JabRef_ja.properties | 8 ++ src/main/resources/l10n/JabRef_nl.properties | 8 ++ src/main/resources/l10n/JabRef_no.properties | 8 ++ .../resources/l10n/JabRef_pt_BR.properties | 8 ++ src/main/resources/l10n/JabRef_ru.properties | 8 ++ src/main/resources/l10n/JabRef_sv.properties | 8 ++ src/main/resources/l10n/JabRef_tr.properties | 8 ++ src/main/resources/l10n/JabRef_vi.properties | 8 ++ src/main/resources/l10n/JabRef_zh.properties | 8 ++ .../jabref/gui/copyfiles/CopyFilesDialog.fxml | 34 +++++ 37 files changed, 620 insertions(+), 25 deletions(-) create mode 100644 src/main/java/org/jabref/gui/copyfiles/CopyFilesAction.java create mode 100644 src/main/java/org/jabref/gui/copyfiles/CopyFilesDialogController.java create mode 100644 src/main/java/org/jabref/gui/copyfiles/CopyFilesDialogView.java create mode 100644 src/main/java/org/jabref/gui/copyfiles/CopyFilesDialogViewModel.java create mode 100644 src/main/java/org/jabref/gui/copyfiles/CopyFilesResultItemViewModel.java create mode 100644 src/main/java/org/jabref/gui/copyfiles/CopyFilesResultListDependency.java create mode 100644 src/main/java/org/jabref/gui/copyfiles/CopyFilesTask.java create mode 100644 src/main/resources/org/jabref/gui/copyfiles/CopyFilesDialog.fxml diff --git a/CHANGELOG.md b/CHANGELOG.md index 216f723d1f1..bc6729986dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `# - We now set the WM_CLASS of the UI to org-jabref-JabRefMain to allow certain Un*x window managers to properly identify its windows - We changed the default paths for the OpenOffice/LibreOffice binaries to the default path for LibreOffice - We no longer create a new entry editor when selecting a new entry to increase performance. [#3187](https://github.com/JabRef/jabref/pull/3187) +- We added the possibility to copy linked files from entries to a single output folder [#2539](https://github.com/JabRef/jabref/pull/2593) - We increased performance and decreased the memory footprint of the entry editor drastically. [#3331](https://github.com/JabRef/jabref/pull/3331) - Late initialization of the context menus in the entry editor. This improves performance and memory footprint further [#3340](https://github.com/JabRef/jabref/pull/3340) - We added a dialog to show that JabRef is working on checking integrity. [#3358](https://github.com/JabRef/jabref/issues/3358) diff --git a/src/main/java/org/jabref/gui/AbstractDialogView.java b/src/main/java/org/jabref/gui/AbstractDialogView.java index 3ab04359a7a..d7880e3cd11 100644 --- a/src/main/java/org/jabref/gui/AbstractDialogView.java +++ b/src/main/java/org/jabref/gui/AbstractDialogView.java @@ -1,10 +1,15 @@ package org.jabref.gui; +import java.util.function.Function; + public abstract class AbstractDialogView extends AbstractView { public AbstractDialogView() { super(); + } + public AbstractDialogView(Function injectionContext) { + super(injectionContext); } public abstract void show(); diff --git a/src/main/java/org/jabref/gui/DialogService.java b/src/main/java/org/jabref/gui/DialogService.java index 2475365ae13..482ca23243e 100644 --- a/src/main/java/org/jabref/gui/DialogService.java +++ b/src/main/java/org/jabref/gui/DialogService.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.Optional; +import javafx.concurrent.Task; import javafx.scene.control.Alert; import javafx.scene.control.ButtonType; import javafx.scene.control.DialogPane; @@ -13,6 +14,8 @@ import org.jabref.gui.util.FileDialogConfiguration; import org.jabref.logic.l10n.Localization; +import org.controlsfx.dialog.ProgressDialog; + /** * This interface provides methods to create dialogs and show them to the user. */ @@ -121,6 +124,12 @@ Optional showCustomButtonDialogAndWait(Alert.AlertType type, String */ Optional showCustomDialogAndWait(Dialog dialog); + /** + * Constructs and shows a canceable {@link ProgressDialog}. Clicking cancel will cancel the underlying service and close the dialog + * @param task The {@link Task} which executes the work and for which to show the dialog + */ + void showCanceableProgressDialogAndWait(Task task); + /** * Notify the user in an non-blocking way (i.e., update status message instead of showing a dialog). * @param message the message to show. @@ -173,4 +182,5 @@ Optional showCustomButtonDialogAndWait(Alert.AlertType type, String * @return A configured instance of the {@link FileChooser} */ FileChooser getConfiguredFileChooser(FileDialogConfiguration fileDialogConfiguration); + } diff --git a/src/main/java/org/jabref/gui/FXDialogService.java b/src/main/java/org/jabref/gui/FXDialogService.java index 6b474d13a2c..3beb1c4b416 100644 --- a/src/main/java/org/jabref/gui/FXDialogService.java +++ b/src/main/java/org/jabref/gui/FXDialogService.java @@ -7,7 +7,9 @@ import java.util.Optional; import java.util.stream.Collectors; +import javafx.concurrent.Task; import javafx.scene.control.Alert.AlertType; +import javafx.scene.control.Button; import javafx.scene.control.ButtonBar; import javafx.scene.control.ButtonType; import javafx.scene.control.DialogPane; @@ -22,6 +24,7 @@ import org.jabref.logic.l10n.Localization; import org.controlsfx.dialog.ExceptionDialog; +import org.controlsfx.dialog.ProgressDialog; /** * This class provides methods to create default @@ -127,6 +130,21 @@ public Optional showCustomDialogAndWait(Dialog dialog) { return dialog.showAndWait(); } + @Override + public void showCanceableProgressDialogAndWait(Task task) { + ProgressDialog progressDialog = new ProgressDialog(task); + progressDialog.setOnCloseRequest(evt -> task.cancel()); + DialogPane dialogPane = progressDialog.getDialogPane(); + dialogPane.getButtonTypes().add(ButtonType.CANCEL); + Button cancelButton = (Button) dialogPane.lookupButton(ButtonType.CANCEL); + cancelButton.setOnAction(evt -> { + task.cancel(); + progressDialog.close(); + }); + progressDialog.showAndWait(); + + } + @Override public void notify(String message) { JabRefGUI.getMainFrame().output(message); diff --git a/src/main/java/org/jabref/gui/JabRefFrame.java b/src/main/java/org/jabref/gui/JabRefFrame.java index 13d1a83366b..3e30b97409c 100644 --- a/src/main/java/org/jabref/gui/JabRefFrame.java +++ b/src/main/java/org/jabref/gui/JabRefFrame.java @@ -80,6 +80,7 @@ import org.jabref.gui.actions.SortTabsAction; import org.jabref.gui.autosaveandbackup.AutosaveUIManager; import org.jabref.gui.bibtexkeypattern.BibtexKeyPatternDialog; +import org.jabref.gui.copyfiles.CopyFilesAction; import org.jabref.gui.customentrytypes.EntryCustomizationDialog; import org.jabref.gui.dbproperties.DatabasePropertiesDialog; import org.jabref.gui.documentviewer.ShowDocumentViewerAction; @@ -389,6 +390,7 @@ public void actionPerformed(ActionEvent e) { Localization.menuTitle("Unabbreviate journal names"), Localization.lang("Unabbreviate journal names of the selected entries"), Globals.getKeyPrefs().getKey(KeyBinding.UNABBREVIATE)); + private final AbstractAction exportLinkedFiles = new CopyFilesAction(); private final AbstractAction manageJournals = new ManageJournalsAction(); private final AbstractAction databaseProperties = new DatabasePropertiesAction(); private final AbstractAction bibtexKeyPattern = new BibtexKeyPatternAction(); @@ -1059,6 +1061,7 @@ private void fillMenu() { file.add(importCurrent); file.add(exportAll); file.add(exportSelected); + file.add(exportLinkedFiles); file.addSeparator(); file.add(connectToSharedDatabaseAction); file.add(pullChangesFromSharedDatabase); @@ -1440,7 +1443,7 @@ dupliCheck, autoSetFile, newEntryAction, newSpec, customizeAction, plainTextImpo twoEntriesOnlyActions.addAll(Arrays.asList(mergeEntries)); atLeastOneEntryActions.clear(); - atLeastOneEntryActions.addAll(Arrays.asList(downloadFullText, lookupIdentifiers)); + atLeastOneEntryActions.addAll(Arrays.asList(downloadFullText, lookupIdentifiers, exportLinkedFiles)); tabbedPane.addChangeListener(event -> updateEnabledState()); diff --git a/src/main/java/org/jabref/gui/copyfiles/CopyFilesAction.java b/src/main/java/org/jabref/gui/copyfiles/CopyFilesAction.java new file mode 100644 index 00000000000..877efee2e37 --- /dev/null +++ b/src/main/java/org/jabref/gui/copyfiles/CopyFilesAction.java @@ -0,0 +1,70 @@ +package org.jabref.gui.copyfiles; + +import java.awt.event.ActionEvent; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Optional; + +import javax.swing.AbstractAction; + +import javafx.concurrent.Task; + +import org.jabref.Globals; +import org.jabref.JabRefExecutorService; +import org.jabref.JabRefGUI; +import org.jabref.gui.DialogService; +import org.jabref.gui.FXDialogService; +import org.jabref.gui.util.DefaultTaskExecutor; +import org.jabref.gui.util.DirectoryDialogConfiguration; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.preferences.JabRefPreferences; + +public class CopyFilesAction extends AbstractAction { + + private final DialogService dialogService = new FXDialogService(); + private BibDatabaseContext databaseContext; + private List entries; + + public CopyFilesAction() { + super(Localization.lang("Copy linked files to folder...")); + } + + @Override + public void actionPerformed(ActionEvent e) { + + DirectoryDialogConfiguration dirDialogConfiguration = new DirectoryDialogConfiguration.Builder() + .withInitialDirectory(Paths.get(Globals.prefs.get(JabRefPreferences.EXPORT_WORKING_DIRECTORY))) + .build(); + entries = JabRefGUI.getMainFrame().getCurrentBasePanel().getSelectedEntries(); + + Optional exportPath = DefaultTaskExecutor + .runInJavaFXThread(() -> dialogService.showDirectorySelectionDialog(dirDialogConfiguration)); + + exportPath.ifPresent(path -> { + databaseContext = JabRefGUI.getMainFrame().getCurrentBasePanel().getDatabaseContext(); + + Task> exportTask = new CopyFilesTask(databaseContext, entries, path); + startServiceAndshowProgessDialog(exportTask); + }); + } + + private void startServiceAndshowProgessDialog(Task> exportService) { + DefaultTaskExecutor.runInJavaFXThread(() -> { + exportService.setOnSucceeded(value -> { + DefaultTaskExecutor.runInJavaFXThread(() -> showDialog(exportService.getValue())); + + }); + JabRefExecutorService.INSTANCE.executeInterruptableTask(exportService); + dialogService.showCanceableProgressDialogAndWait(exportService); + + }); + } + + private void showDialog(List data) { + CopyFilesDialogView dlg = new CopyFilesDialogView(databaseContext, new CopyFilesResultListDependency(data)); + dlg.show(); + } +} \ No newline at end of file diff --git a/src/main/java/org/jabref/gui/copyfiles/CopyFilesDialogController.java b/src/main/java/org/jabref/gui/copyfiles/CopyFilesDialogController.java new file mode 100644 index 00000000000..6c3b79bfa1d --- /dev/null +++ b/src/main/java/org/jabref/gui/copyfiles/CopyFilesDialogController.java @@ -0,0 +1,59 @@ +package org.jabref.gui.copyfiles; + +import javax.inject.Inject; + +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.paint.Color; +import javafx.scene.text.Text; + +import org.jabref.gui.AbstractController; +import org.jabref.gui.util.ValueTableCellFactory; + +import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIcon; +import de.jensd.fx.glyphs.materialdesignicons.utils.MaterialDesignIconFactory; + +public class CopyFilesDialogController extends AbstractController { + + @FXML private TableView tvResult; + @FXML private TableColumn colStatus; + @FXML private TableColumn colMessage; + @FXML private TableColumn colFile; + + @Inject private CopyFilesResultListDependency copyfilesresultlistDependency; //This var must have the same name as the key in the View + + @FXML + private void close(@SuppressWarnings("unused") ActionEvent event) { + getStage().close(); + } + + @FXML + private void initialize() { + viewModel = new CopyFilesDialogViewModel(copyfilesresultlistDependency); + setupTable(); + } + + private void setupTable() { + colFile.setCellValueFactory(cellData -> cellData.getValue().getFile()); + colMessage.setCellValueFactory(cellData -> cellData.getValue().getMessage()); + colStatus.setCellValueFactory(cellData -> cellData.getValue().getIcon()); + + colFile.setCellFactory(new ValueTableCellFactory().withText(item -> item).withTooltip(item -> item)); + colStatus.setCellFactory(new ValueTableCellFactory().withGraphic(item -> { + + Text icon = MaterialDesignIconFactory.get().createIcon(item); + if (item == MaterialDesignIcon.CHECK) { + icon.setFill(Color.GREEN); + } + if (item == MaterialDesignIcon.ALERT) { + icon.setFill(Color.RED); + } + return icon; + })); + + tvResult.setItems(viewModel.copyFilesResultListProperty()); + tvResult.setColumnResizePolicy((param) -> true); + } +} diff --git a/src/main/java/org/jabref/gui/copyfiles/CopyFilesDialogView.java b/src/main/java/org/jabref/gui/copyfiles/CopyFilesDialogView.java new file mode 100644 index 00000000000..7c7c24b5a90 --- /dev/null +++ b/src/main/java/org/jabref/gui/copyfiles/CopyFilesDialogView.java @@ -0,0 +1,36 @@ +package org.jabref.gui.copyfiles; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +import javafx.scene.control.Alert.AlertType; +import javafx.scene.control.DialogPane; + +import org.jabref.gui.AbstractDialogView; +import org.jabref.gui.FXDialog; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.database.BibDatabaseContext; + +public class CopyFilesDialogView extends AbstractDialogView { + + public CopyFilesDialogView(BibDatabaseContext bibDatabaseContext, CopyFilesResultListDependency results) { + super(createContext(bibDatabaseContext, results)); + } + + @Override + public void show() { + FXDialog copyFilesResultDlg = new FXDialog(AlertType.INFORMATION, Localization.lang("Result")); + copyFilesResultDlg.setResizable(true); + copyFilesResultDlg.setDialogPane((DialogPane) this.getView()); + copyFilesResultDlg.show(); + } + + private static Function createContext(BibDatabaseContext bibDatabaseContext, CopyFilesResultListDependency copyfilesresultlistDependency) { + Map context = new HashMap<>(); + //The "keys" of the HashMap must have the same name as the with @inject annotated field in the controller + context.put("bibdatabasecontext", bibDatabaseContext); + context.put("copyfilesresultlistDependency", copyfilesresultlistDependency); + return context::get; + } +} diff --git a/src/main/java/org/jabref/gui/copyfiles/CopyFilesDialogViewModel.java b/src/main/java/org/jabref/gui/copyfiles/CopyFilesDialogViewModel.java new file mode 100644 index 00000000000..912391ea6b5 --- /dev/null +++ b/src/main/java/org/jabref/gui/copyfiles/CopyFilesDialogViewModel.java @@ -0,0 +1,21 @@ +package org.jabref.gui.copyfiles; + +import javafx.beans.property.SimpleListProperty; +import javafx.collections.FXCollections; + +import org.jabref.gui.AbstractViewModel; + +public class CopyFilesDialogViewModel extends AbstractViewModel { + + private final SimpleListProperty copyFilesResultItems = new SimpleListProperty<>( + FXCollections.observableArrayList()); + + public CopyFilesDialogViewModel(CopyFilesResultListDependency results) { + copyFilesResultItems.addAll(results.getResults()); + } + + public SimpleListProperty copyFilesResultListProperty() { + return this.copyFilesResultItems; + } + +} diff --git a/src/main/java/org/jabref/gui/copyfiles/CopyFilesResultItemViewModel.java b/src/main/java/org/jabref/gui/copyfiles/CopyFilesResultItemViewModel.java new file mode 100644 index 00000000000..12fce765c84 --- /dev/null +++ b/src/main/java/org/jabref/gui/copyfiles/CopyFilesResultItemViewModel.java @@ -0,0 +1,42 @@ +package org.jabref.gui.copyfiles; + +import java.nio.file.Path; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; + +import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIcon; + +public class CopyFilesResultItemViewModel { + + private final StringProperty file = new SimpleStringProperty(""); + private final ObjectProperty icon = new SimpleObjectProperty<>(MaterialDesignIcon.ALERT); + private final StringProperty message = new SimpleStringProperty(""); + + public CopyFilesResultItemViewModel(Path file, boolean success, String message) { + this.file.setValue(file.toString()); + this.message.setValue(message); + if (success) { + this.icon.setValue(MaterialDesignIcon.CHECK); + } + } + + public StringProperty getFile() { + return file; + } + + public StringProperty getMessage() { + return message; + } + + public ObjectProperty getIcon() { + return icon; + } + + @Override + public String toString() { + return "CopyFilesResultItemViewModel [file=" + file.get() + ", message=" + message.get() + "]"; + } +} diff --git a/src/main/java/org/jabref/gui/copyfiles/CopyFilesResultListDependency.java b/src/main/java/org/jabref/gui/copyfiles/CopyFilesResultListDependency.java new file mode 100644 index 00000000000..10f09fb27f1 --- /dev/null +++ b/src/main/java/org/jabref/gui/copyfiles/CopyFilesResultListDependency.java @@ -0,0 +1,31 @@ +package org.jabref.gui.copyfiles; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class is a wrapper class for the containing list as it is currently not possible to inject complex object types into FXML controller + * + */ +public class CopyFilesResultListDependency { + + private List results = new ArrayList<>(); + + public CopyFilesResultListDependency() { + //empty, workaround for injection into FXML controller + } + + public CopyFilesResultListDependency(List results) { + this.results = results; + } + + public List getResults() { + return results; + } + + @Override + public String toString() { + return "CopyFilesResultListDependency [results=" + results + "]"; + } + +} diff --git a/src/main/java/org/jabref/gui/copyfiles/CopyFilesTask.java b/src/main/java/org/jabref/gui/copyfiles/CopyFilesTask.java new file mode 100644 index 00000000000..8142f6b328e --- /dev/null +++ b/src/main/java/org/jabref/gui/copyfiles/CopyFilesTask.java @@ -0,0 +1,122 @@ +package org.jabref.gui.copyfiles; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.BiFunction; + +import javafx.concurrent.Task; + +import org.jabref.Globals; +import org.jabref.logic.l10n.Localization; +import org.jabref.logic.util.OS; +import org.jabref.logic.util.io.FileUtil; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.LinkedFile; +import org.jabref.model.util.OptionalUtil; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +public class CopyFilesTask extends Task> { + + private static final Log LOGGER = LogFactory.getLog(CopyFilesAction.class); + private static final String LOGFILE_PREFIX = "copyFileslog_"; + private static final String LOGFILE_EXT = ".log"; + private final BibDatabaseContext databaseContext; + private final Path exportPath; + private final String localizedSucessMessage = Localization.lang("Copied file successfully"); + private final String localizedErrorMessage = Localization.lang("Could not copy file") + ": " + Localization.lang("File exists"); + private final long totalFilesCount; + private final List entries; + private final List results = new ArrayList<>(); + private Optional newPath; + private int numberSucessful; + private int totalFilesCounter; + + private final BiFunction resolvePathFilename = (path, file) -> { + return path.resolve(file.getFileName()); + }; + + public CopyFilesTask(BibDatabaseContext databaseContext, List entries, Path path) { + this.databaseContext = databaseContext; + this.entries = entries; + this.exportPath = path; + totalFilesCount = entries.stream().flatMap(entry -> entry.getFiles().stream()).count(); + + } + + @Override + protected List call() + throws InterruptedException, IOException { + + updateMessage(Localization.lang("Copying files...")); + updateProgress(0, totalFilesCount); + + LocalDateTime currentTime = LocalDateTime.now(); + String currentDate = currentTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss")); + + try (BufferedWriter bw = Files.newBufferedWriter(exportPath.resolve(LOGFILE_PREFIX + currentDate + LOGFILE_EXT), StandardCharsets.UTF_8)) { + + for (int i = 0; i < entries.size(); i++) { + + List files = entries.get(i).getFiles(); + + for (int j = 0; j < files.size(); j++) { + updateMessage(Localization.lang("Copying file %0 of entry %1", Integer.toString(j + 1), Integer.toString(i + 1))); + + LinkedFile fileName = files.get(j); + + Optional fileToExport = fileName.findIn(databaseContext, Globals.prefs.getFileDirectoryPreferences()); + + newPath = OptionalUtil.combine(Optional.of(exportPath), fileToExport, resolvePathFilename); + + newPath.ifPresent(newFile -> { + boolean success = FileUtil.copyFile(fileToExport.get(), newFile, false); + updateProgress(totalFilesCounter++, totalFilesCount); + if (success) { + updateMessage(localizedSucessMessage); + numberSucessful++; + writeLogMessage(newFile, bw, localizedSucessMessage); + addResultToList(newFile, success, localizedSucessMessage); + + } else { + + updateMessage(localizedErrorMessage); + writeLogMessage(newFile, bw, localizedErrorMessage); + addResultToList(newFile, success, localizedErrorMessage); + } + }); + } + } + updateMessage(Localization.lang("Finished copying")); + String sucessMessage = Localization.lang("Copied %0 files of %1 sucessfully to %2", Integer.toString(numberSucessful), Integer.toString(totalFilesCounter), newPath.map(Path::getParent).map(Path::toString).orElse("")); + updateMessage(sucessMessage); + bw.write(sucessMessage); + return results; + } + } + + private void writeLogMessage(Path newFile, BufferedWriter bw, String logMessage) { + try { + bw.write(logMessage + ": " + newFile); + bw.write(OS.NEWLINE); + } catch (IOException e) { + LOGGER.error("error writing log file", e); + } + } + + private void addResultToList(Path newFile, boolean success, String logMessage) { + CopyFilesResultItemViewModel result = new CopyFilesResultItemViewModel(newFile, success, logMessage); + results.add(result); + } + +} diff --git a/src/main/java/org/jabref/gui/menus/RightClickMenu.java b/src/main/java/org/jabref/gui/menus/RightClickMenu.java index 845bbef351e..9de2ba35609 100644 --- a/src/main/java/org/jabref/gui/menus/RightClickMenu.java +++ b/src/main/java/org/jabref/gui/menus/RightClickMenu.java @@ -22,6 +22,7 @@ import org.jabref.gui.IconTheme; import org.jabref.gui.JabRefFrame; import org.jabref.gui.actions.Actions; +import org.jabref.gui.copyfiles.CopyFilesAction; import org.jabref.gui.filelist.FileListTableModel; import org.jabref.gui.keyboard.KeyBinding; import org.jabref.gui.mergeentries.FetchAndMergeEntry; @@ -102,6 +103,7 @@ public RightClickMenu(JabRefFrame frame, BasePanel panel) { add(new GeneralAction(Actions.SEND_AS_EMAIL, Localization.lang("Send as email"), IconTheme.JabRefIcon.EMAIL.getSmallIcon())); addSeparator(); + add(new CopyFilesAction()); JMenu markSpecific = JabRefFrame.subMenu(Localization.menuTitle("Mark specific color")); markSpecific.setIcon(IconTheme.JabRefIcon.MARK_ENTRIES.getSmallIcon()); diff --git a/src/main/java/org/jabref/gui/util/ValueTableCellFactory.java b/src/main/java/org/jabref/gui/util/ValueTableCellFactory.java index 53d7bb7a21b..56dff50f25c 100644 --- a/src/main/java/org/jabref/gui/util/ValueTableCellFactory.java +++ b/src/main/java/org/jabref/gui/util/ValueTableCellFactory.java @@ -4,9 +4,12 @@ import javafx.scene.Node; import javafx.scene.control.TableCell; import javafx.scene.control.TableColumn; +import javafx.scene.control.Tooltip; import javafx.scene.input.MouseEvent; import javafx.util.Callback; +import org.jabref.model.strings.StringUtil; + /** * Constructs a {@link TableCell} based on the value of the cell and a bunch of specified converter methods. * @@ -18,6 +21,7 @@ public class ValueTableCellFactory implements Callback, private Callback toText; private Callback toGraphic; private Callback> toOnMouseClickedEvent; + private Callback toTooltip; public ValueTableCellFactory withText(Callback toText) { this.toText = toText; @@ -29,6 +33,11 @@ public ValueTableCellFactory withGraphic(Callback toGraphic) { return this; } + public ValueTableCellFactory withTooltip(Callback toTooltip) { + this.toTooltip = toTooltip; + return this; + } + public ValueTableCellFactory withOnMouseClickedEvent( Callback> toOnMouseClickedEvent) { this.toOnMouseClickedEvent = toOnMouseClickedEvent; @@ -44,10 +53,11 @@ public TableCell call(TableColumn param) { protected void updateItem(T item, boolean empty) { super.updateItem(item, empty); - if (empty || item == null) { + if (empty || (item == null)) { setText(null); setGraphic(null); setOnMouseClicked(null); + setTooltip(null); } else { if (toText != null) { setText(toText.call(item)); @@ -55,6 +65,12 @@ protected void updateItem(T item, boolean empty) { if (toGraphic != null) { setGraphic(toGraphic.call(item)); } + if (toTooltip != null) { + String tooltipText = toTooltip.call(item); + if (StringUtil.isNotBlank(tooltipText)) { + setTooltip(new Tooltip(tooltipText)); + } + } if (toOnMouseClickedEvent != null) { setOnMouseClicked(toOnMouseClickedEvent.call(item)); } diff --git a/src/main/java/org/jabref/logic/exporter/ExportFormat.java b/src/main/java/org/jabref/logic/exporter/ExportFormat.java index 871d3509136..031b315d5c6 100644 --- a/src/main/java/org/jabref/logic/exporter/ExportFormat.java +++ b/src/main/java/org/jabref/logic/exporter/ExportFormat.java @@ -313,12 +313,6 @@ public void performExport(final BibDatabaseContext databaseContext, final String } - @Override - public void performExport(final BibDatabaseContext databaseContext, Path file, final Charset encoding, - List entries) throws Exception { - performExport(databaseContext, file.getFileName().toString(), encoding, entries); - } - /** * See if there is a name formatter file bundled with this export format. If so, read * all the name formatters so they can be used by the filter layouts. diff --git a/src/main/java/org/jabref/logic/exporter/IExportFormat.java b/src/main/java/org/jabref/logic/exporter/IExportFormat.java index 0b176a6c9c8..b2f5a9b781a 100644 --- a/src/main/java/org/jabref/logic/exporter/IExportFormat.java +++ b/src/main/java/org/jabref/logic/exporter/IExportFormat.java @@ -1,7 +1,6 @@ package org.jabref.logic.exporter; import java.nio.charset.Charset; -import java.nio.file.Path; import java.util.List; import org.jabref.model.database.BibDatabaseContext; @@ -38,18 +37,4 @@ public interface IExportFormat { void performExport(BibDatabaseContext databaseContext, String file, Charset encoding, List entries) throws Exception; - /** - * Perform the Export. - * Gets the path as a java.nio.path instead of a string. - * - * @param databaseContext the database to export from. - * @param file the Path to the file to write to.The path should be an java.nio.Path - * @param encoding The encoding to use. - * @param entries A list containing all entries that - * should be exported. The list of entries must be non null - * @throws Exception - */ - void performExport(BibDatabaseContext databaseContext, Path file, Charset encoding, List entries) - throws Exception; - } diff --git a/src/main/java/org/jabref/logic/pdf/FileAnnotationCache.java b/src/main/java/org/jabref/logic/pdf/FileAnnotationCache.java index bbfdbb031ba..e4b2796aa8a 100644 --- a/src/main/java/org/jabref/logic/pdf/FileAnnotationCache.java +++ b/src/main/java/org/jabref/logic/pdf/FileAnnotationCache.java @@ -25,7 +25,7 @@ public class FileAnnotationCache { /** * Creates an empty fil annotation cache. Required to allow the annotation cache to be injected into views without - * hitting the bug https://github.com/AdamBien/afterburner.fx/issues/71. + * hitting the bug https://github.com/AdamBien/afterburner.fx/issues/71 . */ public FileAnnotationCache() { diff --git a/src/main/java/org/jabref/logic/util/io/FileUtil.java b/src/main/java/org/jabref/logic/util/io/FileUtil.java index 3386aebdcc5..5a734fd47ff 100644 --- a/src/main/java/org/jabref/logic/util/io/FileUtil.java +++ b/src/main/java/org/jabref/logic/util/io/FileUtil.java @@ -31,6 +31,7 @@ import org.apache.commons.logging.LogFactory; public class FileUtil { + public static final boolean IS_POSIX_COMPILANT = FileSystems.getDefault().supportedFileAttributeViews().contains("posix"); public static final int MAXIMUM_FILE_NAME_LENGTH = 255; private static final Log LOGGER = LogFactory.getLog(FileUtil.class); @@ -260,7 +261,7 @@ public static List getListOfLinkedFiles(List bes, List fil */ @Deprecated public static String createFileNameFromPattern(BibDatabase database, BibEntry entry, String fileNamePattern, - LayoutFormatterPreferences prefs) { + LayoutFormatterPreferences prefs) { String targetName = null; StringReader sr = new StringReader(fileNamePattern); diff --git a/src/main/resources/l10n/JabRef_da.properties b/src/main/resources/l10n/JabRef_da.properties index b38abf85f37..a17a7363fe2 100644 --- a/src/main/resources/l10n/JabRef_da.properties +++ b/src/main/resources/l10n/JabRef_da.properties @@ -2333,6 +2333,14 @@ Delete_from_disk= Remove_from_entry= The_group_name_contains_the_keyword_separator_"%0"_and_thus_probably_does_not_work_as_expected.= There_exists_already_a_group_with_the_same_name.= + +Copy_linked_files_to_folder...= +Copied_file_successfully= +Copying_files...= +Copying_file_%0_of_entry_%1= +Finished_copying= +Could_not_copy_file= +Copied_%0_files_of_%1_sucessfully_to_%2= Rename_failed= JabRef_cannot_access_the_file_because_it_is_being_used_by_another_process.= Show_console_output_(only_necessary_when_the_launcher_is_used)= diff --git a/src/main/resources/l10n/JabRef_de.properties b/src/main/resources/l10n/JabRef_de.properties index 773ccdccc7b..7a73f8156ed 100644 --- a/src/main/resources/l10n/JabRef_de.properties +++ b/src/main/resources/l10n/JabRef_de.properties @@ -2333,6 +2333,14 @@ Delete_from_disk=Auf_der_Festplatte_löschen Remove_from_entry=Vom_Eintrag_entfernen The_group_name_contains_the_keyword_separator_"%0"_and_thus_probably_does_not_work_as_expected.=Der_Gruppename_enthält_das_Trennzeichen_"%0"_und_wird_deswegen_möglicherweise_nicht_wie_erwartet_funktionieren. There_exists_already_a_group_with_the_same_name.=Es_existiert_bereits_eine_Gruppe_mit_demselben_Namen. + +Copy_linked_files_to_folder...= +Copied_file_successfully= +Copying_files...=Copying_files... +Copying_file_%0_of_entry_%1= +Finished_copying=Finished_copying +Could_not_copy_file=Could_not_copy_file +Copied_%0_files_of_%1_sucessfully_to_%2= Rename_failed=Umbenennen_fehlgeschlagen JabRef_cannot_access_the_file_because_it_is_being_used_by_another_process.=JabRef_kann_nicht_auf_die_Datei_zugreifen,_da_sie_von_einem_anderen_Prozess_verwendet_wird. Show_console_output_(only_necessary_when_the_launcher_is_used)=Anzeigen_der_Konsolen-Ausgabe_(Nur_notwendig_wenn_der_Launcher_benutzt_wird) diff --git a/src/main/resources/l10n/JabRef_el.properties b/src/main/resources/l10n/JabRef_el.properties index 150eaf23d9c..bc719cbc477 100644 --- a/src/main/resources/l10n/JabRef_el.properties +++ b/src/main/resources/l10n/JabRef_el.properties @@ -2333,6 +2333,14 @@ Delete_from_disk= Remove_from_entry= The_group_name_contains_the_keyword_separator_"%0"_and_thus_probably_does_not_work_as_expected.= There_exists_already_a_group_with_the_same_name.= + +Copy_linked_files_to_folder...= +Copied_file_successfully= +Copying_files...= +Copying_file_%0_of_entry_%1= +Finished_copying= +Could_not_copy_file= +Copied_%0_files_of_%1_sucessfully_to_%2= Rename_failed= JabRef_cannot_access_the_file_because_it_is_being_used_by_another_process.= Show_console_output_(only_necessary_when_the_launcher_is_used)= diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 49f679c064a..fdd40ba016d 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -2333,6 +2333,14 @@ Delete_from_disk=Delete_from_disk Remove_from_entry=Remove_from_entry The_group_name_contains_the_keyword_separator_"%0"_and_thus_probably_does_not_work_as_expected.=The_group_name_contains_the_keyword_separator_"%0"_and_thus_probably_does_not_work_as_expected. There_exists_already_a_group_with_the_same_name.=There_exists_already_a_group_with_the_same_name. + +Copy_linked_files_to_folder...=Copy_linked_files_to_folder... +Copied_file_successfully=Copied_file_successfully +Copying_files...=Copying_files... +Copying_file_%0_of_entry_%1=Copying_file_%0_of_entry_%1 +Finished_copying=Finished_copying +Could_not_copy_file=Could_not_copy_file +Copied_%0_files_of_%1_sucessfully_to_%2=Copied_%0_files_of_%1_sucessfully_to_%2 Rename_failed=Rename_failed JabRef_cannot_access_the_file_because_it_is_being_used_by_another_process.=JabRef_cannot_access_the_file_because_it_is_being_used_by_another_process. Show_console_output_(only_necessary_when_the_launcher_is_used)=Show_console_output_(only_necessary_when_the_launcher_is_used) diff --git a/src/main/resources/l10n/JabRef_es.properties b/src/main/resources/l10n/JabRef_es.properties index 3c6814b1a9e..f4d0cce2060 100644 --- a/src/main/resources/l10n/JabRef_es.properties +++ b/src/main/resources/l10n/JabRef_es.properties @@ -2333,6 +2333,14 @@ Delete_from_disk= Remove_from_entry= The_group_name_contains_the_keyword_separator_"%0"_and_thus_probably_does_not_work_as_expected.= There_exists_already_a_group_with_the_same_name.= + +Copy_linked_files_to_folder...= +Copied_file_successfully= +Copying_files...= +Copying_file_%0_of_entry_%1= +Finished_copying= +Could_not_copy_file= +Copied_%0_files_of_%1_sucessfully_to_%2= Rename_failed= JabRef_cannot_access_the_file_because_it_is_being_used_by_another_process.= Show_console_output_(only_necessary_when_the_launcher_is_used)= diff --git a/src/main/resources/l10n/JabRef_fa.properties b/src/main/resources/l10n/JabRef_fa.properties index 86a38e5b83b..be2841c82e0 100644 --- a/src/main/resources/l10n/JabRef_fa.properties +++ b/src/main/resources/l10n/JabRef_fa.properties @@ -2333,6 +2333,14 @@ Delete_from_disk= Remove_from_entry= The_group_name_contains_the_keyword_separator_"%0"_and_thus_probably_does_not_work_as_expected.= There_exists_already_a_group_with_the_same_name.= + +Copy_linked_files_to_folder...= +Copied_file_successfully= +Copying_files...= +Copying_file_%0_of_entry_%1= +Finished_copying= +Could_not_copy_file= +Copied_%0_files_of_%1_sucessfully_to_%2= Rename_failed= JabRef_cannot_access_the_file_because_it_is_being_used_by_another_process.= Show_console_output_(only_necessary_when_the_launcher_is_used)= diff --git a/src/main/resources/l10n/JabRef_fr.properties b/src/main/resources/l10n/JabRef_fr.properties index 856e9ade784..f9a741f4abf 100644 --- a/src/main/resources/l10n/JabRef_fr.properties +++ b/src/main/resources/l10n/JabRef_fr.properties @@ -2333,6 +2333,15 @@ Delete_from_disk=Supprimer_du_disque Remove_from_entry=Effacer_de_l'entrée The_group_name_contains_the_keyword_separator_"%0"_and_thus_probably_does_not_work_as_expected.=Le_nom_du_groupe_contient_le_séparateur_de_mot-clef_"%0"_et_ne_fonctionnera_probablement_donc_pas_comme_attendu. There_exists_already_a_group_with_the_same_name.=Un_groupe_portant_ce_nom_existe_déjà. + +Copy_linked_files_to_folder...= +Copied_file_successfully= +Copying_files...= +Copying_file_%0_of_entry_%1= +Finished_copying= +Could_not_copy_file= +Copied_%0_files_of_%1_sucessfully_to_%2== + Rename_failed=Échec_du_renommage JabRef_cannot_access_the_file_because_it_is_being_used_by_another_process.=JabRef_ne_peut_pas_accéder_au_fichier_parce_qu'il_est_utilisé_par_un_autre_processus. Show_console_output_(only_necessary_when_the_launcher_is_used)=Afficher_la_sortie_de_la_console_(uniquement_nécessaire_quand_le_lanceur_est_utilisé) diff --git a/src/main/resources/l10n/JabRef_in.properties b/src/main/resources/l10n/JabRef_in.properties index 73a44c921f8..c3b443722cd 100644 --- a/src/main/resources/l10n/JabRef_in.properties +++ b/src/main/resources/l10n/JabRef_in.properties @@ -2333,6 +2333,14 @@ Delete_from_disk= Remove_from_entry= The_group_name_contains_the_keyword_separator_"%0"_and_thus_probably_does_not_work_as_expected.= There_exists_already_a_group_with_the_same_name.= + +Copy_linked_files_to_folder...= +Copied_file_successfully= +Copying_files...= +Copying_file_%0_of_entry_%1= +Finished_copying= +Could_not_copy_file= +Copied_%0_files_of_%1_sucessfully_to_%2= Rename_failed= JabRef_cannot_access_the_file_because_it_is_being_used_by_another_process.= Show_console_output_(only_necessary_when_the_launcher_is_used)= diff --git a/src/main/resources/l10n/JabRef_it.properties b/src/main/resources/l10n/JabRef_it.properties index 61325b44c38..c876043afa5 100644 --- a/src/main/resources/l10n/JabRef_it.properties +++ b/src/main/resources/l10n/JabRef_it.properties @@ -2333,6 +2333,14 @@ Delete_from_disk=Cancella_dal_disco Remove_from_entry=Rimuovi_dalla_voce The_group_name_contains_the_keyword_separator_"%0"_and_thus_probably_does_not_work_as_expected.=Il_nome_di_gruppo_contiene_il_separatore_di_keyword_"%0"_e_quindi_probabilmente_non_funziona_come_ci_si_aspetta. There_exists_already_a_group_with_the_same_name.=Esiste_già_almeno_un_gruppo_con_lo_stesso_nome. + +Copy_linked_files_to_folder...= +Copied_file_successfully= +Copying_files...= +Copying_file_%0_of_entry_%1= +Finished_copying= +Could_not_copy_file= +Copied_%0_files_of_%1_sucessfully_to_%2= Rename_failed= JabRef_cannot_access_the_file_because_it_is_being_used_by_another_process.= Show_console_output_(only_necessary_when_the_launcher_is_used)= diff --git a/src/main/resources/l10n/JabRef_ja.properties b/src/main/resources/l10n/JabRef_ja.properties index ec0cc0d1346..85c529c9017 100644 --- a/src/main/resources/l10n/JabRef_ja.properties +++ b/src/main/resources/l10n/JabRef_ja.properties @@ -2333,6 +2333,14 @@ Delete_from_disk=ディスクから削除 Remove_from_entry=項目から除去 The_group_name_contains_the_keyword_separator_"%0"_and_thus_probably_does_not_work_as_expected.=グループ名にキーワード区切りの「%0」が含まれているので,想定通りにはおそらく動作しません. There_exists_already_a_group_with_the_same_name.=同じ名称のグループがすでに存在します. + +Copy_linked_files_to_folder...= +Copied_file_successfully= +Copying_files...=Copying_files... +Copying_file_%0_of_entry_%1= +Finished_copying=Finished_copying +Could_not_copy_file=Could_not_copy_file +Copied_%0_files_of_%1_sucessfully_to_%2= Rename_failed= JabRef_cannot_access_the_file_because_it_is_being_used_by_another_process.= Show_console_output_(only_necessary_when_the_launcher_is_used)= diff --git a/src/main/resources/l10n/JabRef_nl.properties b/src/main/resources/l10n/JabRef_nl.properties index dfcbd77d6d8..061e94be554 100644 --- a/src/main/resources/l10n/JabRef_nl.properties +++ b/src/main/resources/l10n/JabRef_nl.properties @@ -2333,6 +2333,14 @@ Delete_from_disk= Remove_from_entry= The_group_name_contains_the_keyword_separator_"%0"_and_thus_probably_does_not_work_as_expected.= There_exists_already_a_group_with_the_same_name.= + +Copy_linked_files_to_folder...= +Copied_file_successfully= +Copying_files...= +Copying_file_%0_of_entry_%1= +Finished_copying= +Could_not_copy_file= +Copied_%0_files_of_%1_sucessfully_to_%2= Rename_failed= JabRef_cannot_access_the_file_because_it_is_being_used_by_another_process.= Show_console_output_(only_necessary_when_the_launcher_is_used)= diff --git a/src/main/resources/l10n/JabRef_no.properties b/src/main/resources/l10n/JabRef_no.properties index 5dcfcac2b5b..19d41a20337 100644 --- a/src/main/resources/l10n/JabRef_no.properties +++ b/src/main/resources/l10n/JabRef_no.properties @@ -2333,6 +2333,14 @@ Delete_from_disk= Remove_from_entry= The_group_name_contains_the_keyword_separator_"%0"_and_thus_probably_does_not_work_as_expected.= There_exists_already_a_group_with_the_same_name.= + +Copy_linked_files_to_folder...= +Copied_file_successfully= +Copying_files...= +Copying_file_%0_of_entry_%1= +Finished_copying= +Could_not_copy_file= +Copied_%0_files_of_%1_sucessfully_to_%2= Rename_failed= JabRef_cannot_access_the_file_because_it_is_being_used_by_another_process.= Show_console_output_(only_necessary_when_the_launcher_is_used)= diff --git a/src/main/resources/l10n/JabRef_pt_BR.properties b/src/main/resources/l10n/JabRef_pt_BR.properties index aaa3b456af6..8d4da5028e2 100644 --- a/src/main/resources/l10n/JabRef_pt_BR.properties +++ b/src/main/resources/l10n/JabRef_pt_BR.properties @@ -2333,6 +2333,14 @@ Delete_from_disk= Remove_from_entry= The_group_name_contains_the_keyword_separator_"%0"_and_thus_probably_does_not_work_as_expected.= There_exists_already_a_group_with_the_same_name.= + +Copy_linked_files_to_folder...= +Copied_file_successfully= +Copying_files...= +Copying_file_%0_of_entry_%1= +Finished_copying= +Could_not_copy_file= +Copied_%0_files_of_%1_sucessfully_to_%2= Rename_failed= JabRef_cannot_access_the_file_because_it_is_being_used_by_another_process.= Show_console_output_(only_necessary_when_the_launcher_is_used)= diff --git a/src/main/resources/l10n/JabRef_ru.properties b/src/main/resources/l10n/JabRef_ru.properties index 14fb41637a1..a74a1e9dc51 100644 --- a/src/main/resources/l10n/JabRef_ru.properties +++ b/src/main/resources/l10n/JabRef_ru.properties @@ -2333,6 +2333,14 @@ Delete_from_disk= Remove_from_entry= The_group_name_contains_the_keyword_separator_"%0"_and_thus_probably_does_not_work_as_expected.= There_exists_already_a_group_with_the_same_name.= + +Copy_linked_files_to_folder...= +Copied_file_successfully= +Copying_files...= +Copying_file_%0_of_entry_%1= +Finished_copying= +Could_not_copy_file= +Copied_%0_files_of_%1_sucessfully_to_%2= Rename_failed= JabRef_cannot_access_the_file_because_it_is_being_used_by_another_process.= Show_console_output_(only_necessary_when_the_launcher_is_used)= diff --git a/src/main/resources/l10n/JabRef_sv.properties b/src/main/resources/l10n/JabRef_sv.properties index da91870aae7..d1815ad3fda 100644 --- a/src/main/resources/l10n/JabRef_sv.properties +++ b/src/main/resources/l10n/JabRef_sv.properties @@ -2333,6 +2333,14 @@ Delete_from_disk= Remove_from_entry= The_group_name_contains_the_keyword_separator_"%0"_and_thus_probably_does_not_work_as_expected.= There_exists_already_a_group_with_the_same_name.= + +Copy_linked_files_to_folder...= +Copied_file_successfully= +Copying_files...= +Copying_file_%0_of_entry_%1= +Finished_copying= +Could_not_copy_file= +Copied_%0_files_of_%1_sucessfully_to_%2= Rename_failed= JabRef_cannot_access_the_file_because_it_is_being_used_by_another_process.= Show_console_output_(only_necessary_when_the_launcher_is_used)= diff --git a/src/main/resources/l10n/JabRef_tr.properties b/src/main/resources/l10n/JabRef_tr.properties index 8062bcb2344..acfa2ee58e2 100644 --- a/src/main/resources/l10n/JabRef_tr.properties +++ b/src/main/resources/l10n/JabRef_tr.properties @@ -2333,6 +2333,14 @@ Delete_from_disk=Diskten_sil Remove_from_entry=Girdiden_sil The_group_name_contains_the_keyword_separator_"%0"_and_thus_probably_does_not_work_as_expected.=Grup_adı_anahtar_sözcük_ayracı_olan_"%0"_içeriyor_ve_bu_sebeple_muhtemelen_beklendiğini_gibi_çalışmayacak. There_exists_already_a_group_with_the_same_name.=Aynı_isimli_bir_grup_zaten_var. + +Copy_linked_files_to_folder...= +Copied_file_successfully= +Copying_files...= +Copying_file_%0_of_entry_%1= +Finished_copying= +Could_not_copy_file= +Copied_%0_files_of_%1_sucessfully_to_%2= Rename_failed=Yeniden_adlandırma_başarısız_oldu JabRef_cannot_access_the_file_because_it_is_being_used_by_another_process.=JabRef_dosyaya_erişemiyor_çünkü_dosya_başka_bir_süreç_tarafından_kullanılıyor. Show_console_output_(only_necessary_when_the_launcher_is_used)=Consol_çıktısını_göster_(sadece_başlatıcı_kullanıldığında_gereklidir) diff --git a/src/main/resources/l10n/JabRef_vi.properties b/src/main/resources/l10n/JabRef_vi.properties index 78f0fc78a79..31192bb826e 100644 --- a/src/main/resources/l10n/JabRef_vi.properties +++ b/src/main/resources/l10n/JabRef_vi.properties @@ -2333,6 +2333,14 @@ Delete_from_disk= Remove_from_entry= The_group_name_contains_the_keyword_separator_"%0"_and_thus_probably_does_not_work_as_expected.= There_exists_already_a_group_with_the_same_name.= + +Copy_linked_files_to_folder...= +Copied_file_successfully= +Copying_files...= +Copying_file_%0_of_entry_%1= +Finished_copying= +Could_not_copy_file= +Copied_%0_files_of_%1_sucessfully_to_%2= Rename_failed= JabRef_cannot_access_the_file_because_it_is_being_used_by_another_process.= Show_console_output_(only_necessary_when_the_launcher_is_used)= diff --git a/src/main/resources/l10n/JabRef_zh.properties b/src/main/resources/l10n/JabRef_zh.properties index dcc5316f433..d9f8594fc8d 100644 --- a/src/main/resources/l10n/JabRef_zh.properties +++ b/src/main/resources/l10n/JabRef_zh.properties @@ -2333,6 +2333,14 @@ Delete_from_disk= Remove_from_entry= The_group_name_contains_the_keyword_separator_"%0"_and_thus_probably_does_not_work_as_expected.= There_exists_already_a_group_with_the_same_name.= + +Copy_linked_files_to_folder...= +Copied_file_successfully= +Copying_files...= +Copying_file_%0_of_entry_%1= +Finished_copying= +Could_not_copy_file= +Copied_%0_files_of_%1_sucessfully_to_%2= Rename_failed= JabRef_cannot_access_the_file_because_it_is_being_used_by_another_process.= Show_console_output_(only_necessary_when_the_launcher_is_used)= diff --git a/src/main/resources/org/jabref/gui/copyfiles/CopyFilesDialog.fxml b/src/main/resources/org/jabref/gui/copyfiles/CopyFilesDialog.fxml new file mode 100644 index 00000000000..88aa1b0a03b --- /dev/null +++ b/src/main/resources/org/jabref/gui/copyfiles/CopyFilesDialog.fxml @@ -0,0 +1,34 @@ + + + + + + + + + + + + +
+ + + + + + + + + + +
+ + + +