diff --git a/CHANGELOG.md b/CHANGELOG.md index d12bc7b4c9d..2d23eba0e1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `# - Redesigned group panel. - Number of matched entries is always shown. - The background color of the hit counter signals whether the group contains all/any of the entries selected in the main table. + - Added a possibility to filter the groups panel [#1904](https://github.com/JabRef/jabref/issues/1904) - Removed edit mode. - Redesigned about dialog. - Redesigned key bindings dialog. @@ -45,12 +46,15 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `# - The field `issue` is now always exported to the corresponding `issue` field in MS-Office XML. - We fixed an issue with repeated escaping of the %-sign when running the LaTeXCleanup more than once. [#2451](https://github.com/JabRef/jabref/issues/2451) - We fixed the import of MS-Office XML files, when the `month` field contained an invalid value. - - ArXiV fetcher now checks similarity of entry when using DOI retrieval to avoid false positives. [#2575](https://github.com/JabRef/jabref/issues/2575) - - Sciencedirect/Elsevier fetcher is now able to scrape new HTML structure. [#2576](https://github.com/JabRef/jabref/issues/2576) - - Fixed the synchronization logic of keywords and special fields and vice versa. [#2580](https://github.com/JabRef/jabref/issues/2580) - - We fixed an issue introduced with Version 3.8.2 where executing the `Rename PDFs`-Cleanup operation moved the files to the file directory. [#2526](https://github.com/JabRef/jabref/issues/2526) - - We fixed an issue where the `Move linked files to default file directory`-Cleanup operation did not move the files to the location of the bib-file. [#2454](https://github.com/JabRef/jabref/issues/2454) - - We fixed an issue where executeing `Move file` on a selected file in the `General`-Tab could overwrite an existing file. [#2385](https://github.com/JabRef/jabref/issues/2358) + - ArXiV fetcher now checks similarity of entry when using DOI retrieval to avoid false positives [#2575](https://github.com/JabRef/jabref/issues/2575) + - Sciencedirect/Elsevier fetcher is now able to scrape new HTML structure [#2576](https://github.com/JabRef/jabref/issues/2576) + - Fixed the synchronization logic of keywords and special fields and vice versa [#2580](https://github.com/JabRef/jabref/issues/2580) + - We fixed an issue where the "find unlinked files" functionality threw an error when only one PDF was imported but not assigned to an entry [#2577](https://github.com/JabRef/jabref/issues/2577) + - We fixed issue where escaped braces were incorrectly counted when calculating brace balance in a field [#2561](https://github.com/JabRef/jabref/issues/2561) + - We fixed an issue introduced with Version 3.8.2 where executing the `Rename PDFs`-cleanup operation moved the files to the file directory. [#2526](https://github.com/JabRef/jabref/issues/2526) + - We fixed an issue where the `Move linked files to default file directory`- leanup operation did not move the files to the location of the bib-file. [#2454](https://github.com/JabRef/jabref/issues/2454) + - We fixed an issue where executeing `Move file` on a selected file in the `general`-tab could overwrite an existing file. [#2385](https://github.com/JabRef/jabref/issues/2358) +======= ### Removed diff --git a/src/main/java/org/jabref/cli/ArgumentProcessor.java b/src/main/java/org/jabref/cli/ArgumentProcessor.java index a321718c9b0..1d11dcc723f 100644 --- a/src/main/java/org/jabref/cli/ArgumentProcessor.java +++ b/src/main/java/org/jabref/cli/ArgumentProcessor.java @@ -91,7 +91,7 @@ private static Optional importFile(String argument) { if (address.startsWith("http://") || address.startsWith("https://") || address.startsWith("ftp://")) { // Download web resource to temporary file try { - file = new URLDownload(address).downloadToTemporaryFile(); + file = new URLDownload(address).toTemporaryFile(); } catch (IOException e) { System.err.println(Localization.lang("Problem downloading from %1", address) + e.getLocalizedMessage()); return Optional.empty(); diff --git a/src/main/java/org/jabref/gui/externalfiles/DownloadExternalFile.java b/src/main/java/org/jabref/gui/externalfiles/DownloadExternalFile.java index cff803cde35..ea2b6ff4d1f 100644 --- a/src/main/java/org/jabref/gui/externalfiles/DownloadExternalFile.java +++ b/src/main/java/org/jabref/gui/externalfiles/DownloadExternalFile.java @@ -18,7 +18,6 @@ import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.filelist.FileListEntry; import org.jabref.gui.filelist.FileListEntryEditor; -import org.jabref.gui.net.MonitoredURLDownload; import org.jabref.logic.l10n.Localization; import org.jabref.logic.net.URLDownload; import org.jabref.logic.util.OS; @@ -100,12 +99,12 @@ public void download(URL url, final DownloadCallback callback) throws IOExceptio final File tmp = File.createTempFile("jabref_download", "tmp"); tmp.deleteOnExit(); - URLDownload udl = MonitoredURLDownload.buildMonitoredDownload(frame, url); + URLDownload udl = new URLDownload(url); try { // TODO: what if this takes long time? // TODO: stop editor dialog if this results in an error: - mimeType = udl.determineMimeType(); // Read MIME type + mimeType = udl.getMimeType(); // Read MIME type } catch (IOException ex) { JOptionPane.showMessageDialog(frame, Localization.lang("Invalid URL") + ": " + ex.getMessage(), Localization.lang("Download file"), JOptionPane.ERROR_MESSAGE); @@ -117,7 +116,7 @@ public void download(URL url, final DownloadCallback callback) throws IOExceptio JabRefExecutorService.INSTANCE.execute(() -> { try { - udlF.downloadToFile(tmp); + udlF.toFile(tmp.toPath()); } catch (IOException e2) { dontShowDialog = true; if ((editor != null) && editor.isVisible()) { @@ -344,7 +343,6 @@ private String getSuffix(final String link) { */ @FunctionalInterface public interface DownloadCallback { - void downloadComplete(FileListEntry file); } } diff --git a/src/main/java/org/jabref/gui/groups/AutoGroupDialog.java b/src/main/java/org/jabref/gui/groups/AutoGroupDialog.java deleted file mode 100644 index 0fe42dd5b6d..00000000000 --- a/src/main/java/org/jabref/gui/groups/AutoGroupDialog.java +++ /dev/null @@ -1,264 +0,0 @@ -package org.jabref.gui.groups; - -import java.awt.BorderLayout; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.StringTokenizer; -import java.util.TreeSet; -import java.util.stream.Collectors; - -import javax.swing.AbstractAction; -import javax.swing.ActionMap; -import javax.swing.BorderFactory; -import javax.swing.ButtonGroup; -import javax.swing.InputMap; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JComponent; -import javax.swing.JDialog; -import javax.swing.JPanel; -import javax.swing.JRadioButton; -import javax.swing.JTextField; -import javax.swing.event.CaretEvent; -import javax.swing.event.CaretListener; - -import org.jabref.Globals; -import org.jabref.gui.BasePanel; -import org.jabref.gui.JabRefFrame; -import org.jabref.gui.keyboard.KeyBinding; -import org.jabref.gui.undo.NamedCompound; -import org.jabref.logic.l10n.Localization; -import org.jabref.logic.layout.format.LatexToUnicodeFormatter; -import org.jabref.model.database.BibDatabase; -import org.jabref.model.entry.Author; -import org.jabref.model.entry.AuthorList; -import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.FieldName; -import org.jabref.model.groups.ExplicitGroup; -import org.jabref.model.groups.GroupHierarchyType; -import org.jabref.model.groups.GroupTreeNode; -import org.jabref.model.groups.WordKeywordGroup; -import org.jabref.model.strings.StringUtil; - -import com.jgoodies.forms.builder.ButtonBarBuilder; -import com.jgoodies.forms.builder.FormBuilder; -import com.jgoodies.forms.layout.FormLayout; - -/** - * Dialog for creating or modifying groups. Operates directly on the Vector containing group information. - */ -class AutoGroupDialog extends JDialog implements CaretListener { - - private final JTextField remove = new JTextField(60); - private final JTextField field = new JTextField(60); - private final JTextField deliminator = new JTextField(60); - private final JRadioButton keywords = new JRadioButton( - Localization.lang("Generate groups from keywords in a BibTeX field")); - private final JRadioButton authors = new JRadioButton(Localization.lang("Generate groups for author last names")); - private final JRadioButton editors = new JRadioButton(Localization.lang("Generate groups for editor last names")); - private final JCheckBox useCustomDelimiter = new JCheckBox( - Localization.lang("Use the following delimiter character(s):")); - private final JButton ok = new JButton(Localization.lang("OK")); - private final GroupTreeNodeViewModel m_groupsRoot; - private final JabRefFrame frame; - private final BasePanel panel; - - - /** - * @param groupsRoot The original set of groups, which is required as undo information when all groups are cleared. - */ - public AutoGroupDialog(JabRefFrame jabrefFrame, BasePanel basePanel, - GroupTreeNodeViewModel groupsRoot, String defaultField, String defaultRemove, String defaultDeliminator) { - super(jabrefFrame, Localization.lang("Automatically create groups"), true); - frame = jabrefFrame; - panel = basePanel; - m_groupsRoot = groupsRoot; - field.setText(defaultField); - remove.setText(defaultRemove); - deliminator.setText(defaultDeliminator); - useCustomDelimiter.setSelected(true); - ActionListener okListener = e -> { - dispose(); - - try { - GroupTreeNode autoGroupsRoot = GroupTreeNode.fromGroup( - new ExplicitGroup(Localization.lang("Automatically created groups"), - GroupHierarchyType.INCLUDING, - Globals.prefs.getKeywordDelimiter())); - Set keywords; - String fieldText = field.getText().toLowerCase().trim(); - if (this.keywords.isSelected()) { - if (useCustomDelimiter.isSelected()) { - keywords = findDeliminatedWordsInField(panel.getDatabase(), fieldText, - deliminator.getText()); - } else { - keywords = findAllWordsInField(panel.getDatabase(), fieldText, remove.getText()); - - } - } else if (authors.isSelected()) { - List fields = new ArrayList<>(2); - fields.add(FieldName.AUTHOR); - keywords = findAuthorLastNames(panel.getDatabase(), fields); - fieldText = FieldName.AUTHOR; - } else { // editors.isSelected() as it is a radio button group. - List fields = new ArrayList<>(2); - fields.add(FieldName.EDITOR); - keywords = findAuthorLastNames(panel.getDatabase(), fields); - fieldText = FieldName.EDITOR; - } - - LatexToUnicodeFormatter formatter = new LatexToUnicodeFormatter(); - - for (String keyword : keywords) { - WordKeywordGroup group = new WordKeywordGroup( - formatter.format(keyword), GroupHierarchyType.INDEPENDENT, fieldText, keyword, false, Globals.prefs.getKeywordDelimiter(), false); - autoGroupsRoot.addChild(GroupTreeNode.fromGroup(group)); - } - - autoGroupsRoot.moveTo(m_groupsRoot.getNode()); - NamedCompound ce = new NamedCompound(Localization.lang("Automatically create groups")); - UndoableAddOrRemoveGroup undo = new UndoableAddOrRemoveGroup(m_groupsRoot, new GroupTreeNodeViewModel(autoGroupsRoot), UndoableAddOrRemoveGroup.ADD_NODE); - ce.addEdit(undo); - - panel.markBaseChanged(); // a change always occurs - frame.output(Localization.lang("Created groups.")); - ce.end(); - panel.getUndoManager().addEdit(ce); - } catch (IllegalArgumentException exception) { - frame.showMessage(exception.getLocalizedMessage()); - } - }; - remove.addActionListener(okListener); - field.addActionListener(okListener); - field.addCaretListener(this); - AbstractAction cancelAction = new AbstractAction() { - - @Override - public void actionPerformed(ActionEvent e) { - dispose(); - } - }; - JButton cancel = new JButton(Localization.lang("Cancel")); - cancel.addActionListener(cancelAction); - ok.addActionListener(okListener); - // Key bindings: - JPanel main = new JPanel(); - ActionMap am = main.getActionMap(); - InputMap im = main.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); - im.put(Globals.getKeyPrefs().getKey(KeyBinding.CLOSE_DIALOG), "close"); - am.put("close", cancelAction); - - ButtonGroup bg = new ButtonGroup(); - bg.add(keywords); - bg.add(authors); - bg.add(editors); - keywords.setSelected(true); - - FormBuilder b = FormBuilder.create(); - b.layout(new FormLayout("left:20dlu, 4dlu, left:pref, 4dlu, fill:60dlu", - "p, 2dlu, p, 2dlu, p, 2dlu, p, 2dlu, p, 2dlu, p")); - b.add(keywords).xyw(1, 1, 5); - b.add(Localization.lang("Field to group by") + ":").xy(3, 3); - b.add(field).xy(5, 3); - b.add(Localization.lang("Characters to ignore") + ":").xy(3, 5); - b.add(remove).xy(5, 5); - b.add(useCustomDelimiter).xy(3, 7); - b.add(deliminator).xy(5, 7); - b.add(authors).xyw(1, 9, 5); - b.add(editors).xyw(1, 11, 5); - b.build(); - b.border(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - - JPanel opt = new JPanel(); - ButtonBarBuilder bb = new ButtonBarBuilder(opt); - bb.addGlue(); - bb.addButton(ok); - bb.addButton(cancel); - bb.addGlue(); - - main.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - opt.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - getContentPane().add(main, BorderLayout.CENTER); - getContentPane().add(b.getPanel(), BorderLayout.CENTER); - getContentPane().add(opt, BorderLayout.SOUTH); - - updateComponents(); - pack(); - setLocationRelativeTo(frame); - } - - public static Set findDeliminatedWordsInField(BibDatabase db, String field, String deliminator) { - Set res = new TreeSet<>(); - - for (BibEntry be : db.getEntries()) { - be.getField(field).ifPresent(fieldValue -> { - StringTokenizer tok = new StringTokenizer(fieldValue.trim(), deliminator); - while (tok.hasMoreTokens()) { - res.add(StringUtil.capitalizeFirst(tok.nextToken().trim())); - } - }); - } - return res; - } - - /** - * Returns a Set containing all words used in the database in the given field type. Characters in - * remove are not included. - * - * @param db a BibDatabase value - * @param field a String value - * @param remove a String value - * @return a Set value - */ - public static Set findAllWordsInField(BibDatabase db, String field, String remove) { - Set res = new TreeSet<>(); - for (BibEntry be : db.getEntries()) { - be.getField(field).ifPresent(o -> { - StringTokenizer tok = new StringTokenizer(o, remove, false); - while (tok.hasMoreTokens()) { - res.add(StringUtil.capitalizeFirst(tok.nextToken().trim())); - } - }); - } - return res; - } - - /** - * Finds all authors' last names in all the given fields for the given database. - * - * @param db The database. - * @param fields The fields to look in. - * @return a set containing the names. - */ - public static Set findAuthorLastNames(BibDatabase db, List fields) { - Set res = new TreeSet<>(); - for (BibEntry be : db.getEntries()) { - for (String field : fields) { - be.getField(field).ifPresent(val -> { - if (!val.isEmpty()) { - AuthorList al = AuthorList.parse(val); - res.addAll(al.getAuthors().stream().map(Author::getLast).filter(Optional::isPresent) - .map(Optional::get).filter(lastName -> !lastName.isEmpty()) - .collect(Collectors.toList())); - } - }); - } - } - - return res; - } - - @Override - public void caretUpdate(CaretEvent e) { - updateComponents(); - } - - private void updateComponents() { - String groupField = field.getText().trim(); - ok.setEnabled(groupField.matches("\\w+")); - } -} diff --git a/src/main/java/org/jabref/gui/groups/EntryTableTransferHandler.java b/src/main/java/org/jabref/gui/groups/EntryTableTransferHandler.java index 2b6fcf131f1..213436683f9 100644 --- a/src/main/java/org/jabref/gui/groups/EntryTableTransferHandler.java +++ b/src/main/java/org/jabref/gui/groups/EntryTableTransferHandler.java @@ -32,7 +32,7 @@ import org.jabref.gui.importer.ImportMenuItem; import org.jabref.gui.importer.actions.OpenDatabaseAction; import org.jabref.gui.maintable.MainTable; -import org.jabref.gui.net.MonitoredURLDownload; +import org.jabref.logic.net.URLDownload; import org.jabref.logic.util.io.FileUtil; import org.jabref.pdfimport.PdfImporter; import org.jabref.pdfimport.PdfImporter.ImportPdfFilesResult; @@ -377,10 +377,7 @@ private boolean handleDropTransfer(URL dropLink) throws IOException { File tmpfile = File.createTempFile("jabrefimport", ""); tmpfile.deleteOnExit(); - // System.out.println("Import url: " + dropLink.toString()); - // System.out.println("Temp file: "+tmpfile.getAbsolutePath()); - - MonitoredURLDownload.buildMonitoredDownload(entryTable, dropLink).downloadToFile(tmpfile); + new URLDownload(dropLink).toFile(tmpfile.toPath()); // Import into new if entryTable==null, otherwise into current library: ImportMenuItem importer = new ImportMenuItem(frame, entryTable == null); diff --git a/src/main/java/org/jabref/gui/groups/GroupDialog.java b/src/main/java/org/jabref/gui/groups/GroupDialog.java index 45fd491992d..e5713c36c45 100644 --- a/src/main/java/org/jabref/gui/groups/GroupDialog.java +++ b/src/main/java/org/jabref/gui/groups/GroupDialog.java @@ -38,6 +38,8 @@ import org.jabref.logic.search.SearchQuery; import org.jabref.model.entry.FieldName; import org.jabref.model.groups.AbstractGroup; +import org.jabref.model.groups.AutomaticKeywordGroup; +import org.jabref.model.groups.AutomaticPersonsGroup; import org.jabref.model.groups.ExplicitGroup; import org.jabref.model.groups.GroupHierarchyType; import org.jabref.model.groups.RegexKeywordGroup; @@ -48,6 +50,7 @@ import com.jgoodies.forms.builder.ButtonBarBuilder; import com.jgoodies.forms.builder.DefaultFormBuilder; +import com.jgoodies.forms.builder.FormBuilder; import com.jgoodies.forms.layout.FormLayout; /** @@ -59,6 +62,7 @@ class GroupDialog extends JDialog implements Dialog { private static final int INDEX_EXPLICIT_GROUP = 0; private static final int INDEX_KEYWORD_GROUP = 1; private static final int INDEX_SEARCH_GROUP = 2; + private static final int INDEX_AUTO_GROUP = 3; private static final int TEXTFIELD_LENGTH = 30; // for all types private final JTextField nameField = new JTextField(GroupDialog.TEXTFIELD_LENGTH); @@ -68,6 +72,8 @@ class GroupDialog extends JDialog implements Dialog { Localization.lang("Dynamically group entries by searching a field for a keyword")); private final JRadioButton searchRadioButton = new JRadioButton( Localization.lang("Dynamically group entries by a free-form search expression")); + private final JRadioButton autoRadioButton = new JRadioButton( + Localization.lang("Automatically create groups")); private final JRadioButton independentButton = new JRadioButton( Localization.lang("Independent group: When selected, view only this group's entries")); private final JRadioButton intersectionButton = new JRadioButton( @@ -83,6 +89,15 @@ class GroupDialog extends JDialog implements Dialog { private final JTextField searchGroupSearchExpression = new JTextField(GroupDialog.TEXTFIELD_LENGTH); private final JCheckBox searchGroupCaseSensitive = new JCheckBox(Localization.lang("Case sensitive")); private final JCheckBox searchGroupRegExp = new JCheckBox(Localization.lang("regular expression")); + // for AutoGroup + private final JRadioButton autoGroupKeywordsOption = new JRadioButton( + Localization.lang("Generate groups from keywords in a BibTeX field")); + private final JTextField autoGroupKeywordsField = new JTextField(60); + private final JTextField autoGroupKeywordsDeliminator = new JTextField(60); + private final JRadioButton autoGroupPersonsOption = new JRadioButton( + Localization.lang("Generate groups for author last names")); + private final JTextField autoGroupPersonsField = new JTextField(60); + // for all types private final JButton okButton = new JButton(Localization.lang("OK")); private final JPanel optionsPanel = new JPanel(); @@ -119,6 +134,7 @@ public GroupDialog(JabRefFrame jabrefFrame, AbstractGroup editedGroup) { groupType.add(explicitRadioButton); groupType.add(keywordsRadioButton); groupType.add(searchRadioButton); + groupType.add(autoRadioButton); ButtonGroup groupHierarchy = new ButtonGroup(); groupHierarchy.add(independentButton); groupHierarchy.add(intersectionButton); @@ -154,6 +170,31 @@ public GroupDialog(JabRefFrame jabrefFrame, AbstractGroup editedGroup) { builderSG.nextLine(); builderSG.append(searchGroupRegExp, 3); optionsPanel.add(builderSG.getPanel(), String.valueOf(GroupDialog.INDEX_SEARCH_GROUP)); + + // for auto group + ButtonGroup bg = new ButtonGroup(); + bg.add(autoGroupKeywordsOption); + bg.add(autoGroupPersonsOption); + + FormLayout layoutAutoGroup = new FormLayout("left:20dlu, 4dlu, left:pref, 4dlu, fill:60dlu", + "p, 2dlu, p, 2dlu, p, 2dlu, p, 2dlu, p"); + FormBuilder builderAutoGroup = FormBuilder.create(); + builderAutoGroup.layout(layoutAutoGroup); + builderAutoGroup.add(autoGroupKeywordsOption).xyw(1, 1, 5); + builderAutoGroup.add(Localization.lang("Field to group by") + ":").xy(3, 3); + builderAutoGroup.add(autoGroupKeywordsField).xy(5, 3); + builderAutoGroup.add(Localization.lang("Use the following delimiter character(s):")).xy(3, 5); + builderAutoGroup.add(autoGroupKeywordsDeliminator).xy(5, 5); + builderAutoGroup.add(autoGroupPersonsOption).xyw(1, 7, 5); + builderAutoGroup.add(Localization.lang("Field to group by") + ":").xy(3, 9); + builderAutoGroup.add(autoGroupPersonsField).xy(5, 9); + optionsPanel.add(builderAutoGroup.build(), String.valueOf(GroupDialog.INDEX_AUTO_GROUP)); + + autoGroupKeywordsOption.setSelected(true); + autoGroupKeywordsField.setText(Globals.prefs.get(JabRefPreferences.GROUPS_DEFAULT_FIELD)); + autoGroupKeywordsDeliminator.setText(Globals.prefs.get(JabRefPreferences.KEYWORD_SEPARATOR)); + autoGroupPersonsField.setText(FieldName.AUTHOR); + // ... for buttons panel FormLayout layoutBP = new FormLayout("pref, 4dlu, pref", "p"); layoutBP.setColumnGroups(new int[][] {{1, 3}}); @@ -168,7 +209,7 @@ public GroupDialog(JabRefFrame jabrefFrame, AbstractGroup editedGroup) { // create layout FormLayout layoutAll = new FormLayout( "right:pref, 4dlu, fill:600px, 4dlu, fill:pref", - "p, 3dlu, p, 3dlu, p, 0dlu, p, 0dlu, p, 3dlu, p, 3dlu, p, " + "p, 3dlu, p, 3dlu, p, 0dlu, p, 0dlu, p, 0dlu, p, 3dlu, p, 3dlu, p, " + "0dlu, p, 0dlu, p, 3dlu, p, 3dlu, " + "p, 3dlu, p, 3dlu, top:80dlu, 9dlu, p, 9dlu, p"); @@ -189,6 +230,9 @@ public GroupDialog(JabRefFrame jabrefFrame, AbstractGroup editedGroup) { builderAll.append(searchRadioButton, 5); builderAll.nextLine(); builderAll.nextLine(); + builderAll.append(autoRadioButton, 5); + builderAll.nextLine(); + builderAll.nextLine(); builderAll.appendSeparator(Localization.lang("Hierarchical context")); builderAll.nextLine(); builderAll.nextLine(); @@ -243,6 +287,7 @@ public Dimension getPreferredSize() { explicitRadioButton.addItemListener(radioButtonItemListener); keywordsRadioButton.addItemListener(radioButtonItemListener); searchRadioButton.addItemListener(radioButtonItemListener); + autoRadioButton.addItemListener(radioButtonItemListener); Action cancelAction = new AbstractAction() { @@ -284,6 +329,15 @@ public void actionPerformed(ActionEvent e) { } catch (Exception e1) { // should never happen } + } else if (autoRadioButton.isSelected()) { + if (autoGroupKeywordsOption.isSelected()) { + resultingGroup = new AutomaticKeywordGroup(nameField.getText().trim(), getContext(), + autoGroupKeywordsField.getText().trim(), + autoGroupKeywordsDeliminator.getText().charAt(0)); + } else { + resultingGroup = new AutomaticPersonsGroup(nameField.getText().trim(), getContext(), + autoGroupPersonsField.getText().trim()); + } } dispose(); } catch (IllegalArgumentException exception) { @@ -304,39 +358,55 @@ public void actionPerformed(ActionEvent e) { searchGroupCaseSensitive.addItemListener(itemListener); // configure for current type - if ((editedGroup != null) && (editedGroup.getClass() == WordKeywordGroup.class)) { - WordKeywordGroup group = (WordKeywordGroup) editedGroup; - nameField.setText(group.getName()); - keywordGroupSearchField.setText(group.getSearchField()); - keywordGroupSearchTerm.setText(group.getSearchExpression()); - keywordGroupCaseSensitive.setSelected(group.isCaseSensitive()); - keywordGroupRegExp.setSelected(false); - keywordsRadioButton.setSelected(true); - setContext(editedGroup.getHierarchicalContext()); - } else if ((editedGroup != null) && (editedGroup.getClass() == RegexKeywordGroup.class)) { - RegexKeywordGroup group = (RegexKeywordGroup) editedGroup; - nameField.setText(group.getName()); - keywordGroupSearchField.setText(group.getSearchField()); - keywordGroupSearchTerm.setText(group.getSearchExpression()); - keywordGroupCaseSensitive.setSelected(group.isCaseSensitive()); - keywordGroupRegExp.setSelected(true); - keywordsRadioButton.setSelected(true); - setContext(editedGroup.getHierarchicalContext()); - } else if ((editedGroup != null) && (editedGroup.getClass() == SearchGroup.class)) { - SearchGroup group = (SearchGroup) editedGroup; - nameField.setText(group.getName()); - searchGroupSearchExpression.setText(group.getSearchExpression()); - searchGroupCaseSensitive.setSelected(group.isCaseSensitive()); - searchGroupRegExp.setSelected(group.isRegularExpression()); - searchRadioButton.setSelected(true); - setContext(editedGroup.getHierarchicalContext()); - } else if ((editedGroup != null) && (editedGroup.getClass() == ExplicitGroup.class)) { - nameField.setText(editedGroup.getName()); - explicitRadioButton.setSelected(true); - setContext(editedGroup.getHierarchicalContext()); - } else { // creating new group -> defaults! + if (editedGroup == null) { + // creating new group -> defaults! explicitRadioButton.setSelected(true); setContext(GroupHierarchyType.INDEPENDENT); + } else { + if (editedGroup.getClass() == WordKeywordGroup.class) { + WordKeywordGroup group = (WordKeywordGroup) editedGroup; + nameField.setText(group.getName()); + keywordGroupSearchField.setText(group.getSearchField()); + keywordGroupSearchTerm.setText(group.getSearchExpression()); + keywordGroupCaseSensitive.setSelected(group.isCaseSensitive()); + keywordGroupRegExp.setSelected(false); + keywordsRadioButton.setSelected(true); + setContext(editedGroup.getHierarchicalContext()); + } else if (editedGroup.getClass() == RegexKeywordGroup.class) { + RegexKeywordGroup group = (RegexKeywordGroup) editedGroup; + nameField.setText(group.getName()); + keywordGroupSearchField.setText(group.getSearchField()); + keywordGroupSearchTerm.setText(group.getSearchExpression()); + keywordGroupCaseSensitive.setSelected(group.isCaseSensitive()); + keywordGroupRegExp.setSelected(true); + keywordsRadioButton.setSelected(true); + setContext(editedGroup.getHierarchicalContext()); + } else if (editedGroup.getClass() == SearchGroup.class) { + SearchGroup group = (SearchGroup) editedGroup; + nameField.setText(group.getName()); + searchGroupSearchExpression.setText(group.getSearchExpression()); + searchGroupCaseSensitive.setSelected(group.isCaseSensitive()); + searchGroupRegExp.setSelected(group.isRegularExpression()); + searchRadioButton.setSelected(true); + setContext(editedGroup.getHierarchicalContext()); + } else if (editedGroup.getClass() == ExplicitGroup.class) { + nameField.setText(editedGroup.getName()); + explicitRadioButton.setSelected(true); + setContext(editedGroup.getHierarchicalContext()); + } else if (editedGroup.getClass() == AutomaticKeywordGroup.class) { + nameField.setText(editedGroup.getName()); + autoRadioButton.setSelected(true); + setContext(editedGroup.getHierarchicalContext()); + + if (editedGroup.getClass() == AutomaticKeywordGroup.class) { + AutomaticKeywordGroup group = (AutomaticKeywordGroup) editedGroup; + autoGroupKeywordsDeliminator.setText(group.getKeywordSeperator().toString()); + autoGroupKeywordsField.setText(group.getField()); + } else if (editedGroup.getClass() == AutomaticPersonsGroup.class) { + AutomaticPersonsGroup group = (AutomaticPersonsGroup) editedGroup; + autoGroupPersonsField.setText(group.getField()); + } + } } } @@ -385,6 +455,8 @@ private void setLayoutForSelectedGroup() { optionsLayout.show(optionsPanel, String.valueOf(GroupDialog.INDEX_KEYWORD_GROUP)); } else if (searchRadioButton.isSelected()) { optionsLayout.show(optionsPanel, String.valueOf(GroupDialog.INDEX_SEARCH_GROUP)); + } else if (autoRadioButton.isSelected()) { + optionsLayout.show(optionsPanel, String.valueOf(GroupDialog.INDEX_AUTO_GROUP)); } } diff --git a/src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java b/src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java index bca890ca228..578465c411c 100644 --- a/src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java +++ b/src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java @@ -1,29 +1,40 @@ package org.jabref.gui.groups; +import java.util.Map; import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; import javafx.application.Platform; import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanBinding; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleIntegerProperty; +import javafx.collections.FXCollections; import javafx.collections.ObservableList; import org.jabref.gui.StateManager; import org.jabref.gui.util.BindingsHelper; import org.jabref.logic.l10n.Localization; +import org.jabref.logic.layout.format.LatexToUnicodeFormatter; import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.event.EntryEvent; import org.jabref.model.groups.AbstractGroup; import org.jabref.model.groups.AllEntriesGroup; +import org.jabref.model.groups.AutomaticGroup; import org.jabref.model.groups.GroupTreeNode; +import org.jabref.model.strings.StringUtil; import com.google.common.eventbus.Subscribe; import org.fxmisc.easybind.EasyBind; public class GroupNodeViewModel { - private final String name; + private final String displayName; private final boolean isRoot; private final String iconCode; private final ObservableList children; @@ -38,10 +49,24 @@ public GroupNodeViewModel(BibDatabaseContext databaseContext, StateManager state this.databaseContext = Objects.requireNonNull(databaseContext); this.groupNode = Objects.requireNonNull(groupNode); - name = groupNode.getName(); + LatexToUnicodeFormatter formatter = new LatexToUnicodeFormatter(); + displayName = formatter.format(groupNode.getName()); isRoot = groupNode.isRoot(); iconCode = ""; - children = EasyBind.map(groupNode.getChildren(), child -> new GroupNodeViewModel(databaseContext, stateManager, child)); + if (groupNode.getGroup() instanceof AutomaticGroup) { + AutomaticGroup automaticGroup = (AutomaticGroup) groupNode.getGroup(); + + // TODO: Update on changes to entry list (however: there is no flatMap and filter as observable TransformationLists) + children = databaseContext.getDatabase() + .getEntries().stream() + .flatMap(stream -> createSubgroups(databaseContext, stateManager, automaticGroup, stream)) + .filter(distinctByKey(group -> group.getGroupNode().getName())) + .sorted((group1, group2) -> group1.getDisplayName().compareToIgnoreCase(group2.getDisplayName())) + .collect(Collectors.toCollection(FXCollections::observableArrayList)); + } else { + children = EasyBind.map(groupNode.getChildren(), + child -> new GroupNodeViewModel(databaseContext, stateManager, child)); + } hasChildren = new SimpleBooleanProperty(); hasChildren.bind(Bindings.isNotEmpty(children)); hits = new SimpleIntegerProperty(0); @@ -59,10 +84,20 @@ public GroupNodeViewModel(BibDatabaseContext databaseContext, StateManager state this(databaseContext, stateManager, new GroupTreeNode(group)); } + private static Predicate distinctByKey(Function keyExtractor) { + Map seen = new ConcurrentHashMap<>(); + return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null; + } + static GroupNodeViewModel getAllEntriesGroup(BibDatabaseContext newDatabase, StateManager stateManager) { return new GroupNodeViewModel(newDatabase, stateManager, new AllEntriesGroup(Localization.lang("All entries"))); } + private Stream createSubgroups(BibDatabaseContext databaseContext, StateManager stateManager, AutomaticGroup automaticGroup, BibEntry entry) { + return automaticGroup.createSubgroups(entry).stream() + .map(child -> new GroupNodeViewModel(databaseContext, stateManager, child)); + } + public SimpleBooleanProperty expandedProperty() { return expandedProperty; } @@ -79,8 +114,8 @@ public SimpleBooleanProperty hasChildrenProperty() { return hasChildren; } - public String getName() { - return name; + public String getDisplayName() { + return displayName; } public boolean isRoot() { @@ -88,7 +123,7 @@ public boolean isRoot() { } public String getDescription() { - return "Some group named " + getName(); + return "Some group named " + getDisplayName(); } public SimpleIntegerProperty getHits() { @@ -102,19 +137,14 @@ public boolean equals(Object o) { GroupNodeViewModel that = (GroupNodeViewModel) o; - if (isRoot != that.isRoot) return false; - if (!name.equals(that.name)) return false; - if (!iconCode.equals(that.iconCode)) return false; - if (!children.equals(that.children)) return false; - if (!databaseContext.equals(that.databaseContext)) return false; if (!groupNode.equals(that.groupNode)) return false; - return hits.getValue().equals(that.hits.getValue()); + return true; } @Override public String toString() { return "GroupNodeViewModel{" + - "name='" + name + '\'' + + "displayName='" + displayName + '\'' + ", isRoot=" + isRoot + ", iconCode='" + iconCode + '\'' + ", children=" + children + @@ -126,14 +156,7 @@ public String toString() { @Override public int hashCode() { - int result = name.hashCode(); - result = 31 * result + (isRoot ? 1 : 0); - result = 31 * result + iconCode.hashCode(); - result = 31 * result + children.hashCode(); - result = 31 * result + databaseContext.hashCode(); - result = 31 * result + groupNode.hashCode(); - result = 31 * result + hits.hashCode(); - return result; + return groupNode.hashCode(); } public String getIconCode() { @@ -173,4 +196,8 @@ public GroupTreeNode addSubgroup(AbstractGroup subgroup) { void toggleExpansion() { expandedProperty().set(!expandedProperty().get()); } + + boolean isMatchedBy(String searchString) { + return StringUtil.isBlank(searchString) || getDisplayName().contains(searchString); + } } diff --git a/src/main/java/org/jabref/gui/groups/GroupSelector.java b/src/main/java/org/jabref/gui/groups/GroupSelector.java index 16cf18acb44..f94467717e3 100644 --- a/src/main/java/org/jabref/gui/groups/GroupSelector.java +++ b/src/main/java/org/jabref/gui/groups/GroupSelector.java @@ -205,7 +205,6 @@ public void stateChanged(ChangeEvent event) { JButton helpButton = new HelpAction(Localization.lang("Help on groups"), HelpFile.GROUP) .getHelpButton(); - JButton autoGroup = new JButton(IconTheme.JabRefIcon.AUTO_GROUP.getSmallIcon()); Insets butIns = new Insets(0, 0, 0, 0); helpButton.setMargin(butIns); openSettings.setMargin(butIns); @@ -213,20 +212,12 @@ public void stateChanged(ChangeEvent event) { orCb.addActionListener(e -> valueChanged(null)); invCb.addActionListener(e -> valueChanged(null)); showOverlappingGroups.addActionListener(e -> valueChanged(null)); - autoGroup.addActionListener(e -> { - AutoGroupDialog gd = new AutoGroupDialog(frame, panel, groupsRoot, - Globals.prefs.get(JabRefPreferences.GROUPS_DEFAULT_FIELD), " .,", - Globals.prefs.get(JabRefPreferences.KEYWORD_SEPARATOR)); - gd.setVisible(true); - // gd does the operation itself - }); floatCb.addActionListener(e -> valueChanged(null)); highlCb.addActionListener(e -> valueChanged(null)); hideNonHits.addActionListener(e -> valueChanged(null)); grayOut.addActionListener(e -> valueChanged(null)); andCb.setToolTipText(Localization.lang("Display only entries belonging to all selected groups.")); orCb.setToolTipText(Localization.lang("Display all entries belonging to one or more of the selected groups.")); - autoGroup.setToolTipText(Localization.lang("Automatically create groups for library.")); openSettings.setToolTipText(Localization.lang("Settings")); invCb.setToolTipText("" + Localization.lang("Show entries not in group selection") + ""); showOverlappingGroups.setToolTipText( @@ -253,8 +244,6 @@ public void stateChanged(ChangeEvent event) { con.gridx = 0; con.gridx = 1; - gbl.setConstraints(autoGroup, con); - rootPanel.add(autoGroup); con.gridx = 2; gbl.setConstraints(openSettings, con); diff --git a/src/main/java/org/jabref/gui/groups/GroupTree.css b/src/main/java/org/jabref/gui/groups/GroupTree.css index 0c36d609d15..284c08f7f68 100644 --- a/src/main/java/org/jabref/gui/groups/GroupTree.css +++ b/src/main/java/org/jabref/gui/groups/GroupTree.css @@ -107,7 +107,7 @@ -fx-translate-x: -5px; } -#buttonBarBottom { +#barBottom { -fx-background-color: #dadad8; -fx-border-color: dimgray; -fx-border-width: 1 0 0 0; diff --git a/src/main/java/org/jabref/gui/groups/GroupTree.fxml b/src/main/java/org/jabref/gui/groups/GroupTree.fxml index 571694c45af..f0ce56026e6 100644 --- a/src/main/java/org/jabref/gui/groups/GroupTree.fxml +++ b/src/main/java/org/jabref/gui/groups/GroupTree.fxml @@ -6,6 +6,8 @@ + + @@ -24,18 +26,21 @@ - - - - - + + + + + + + + diff --git a/src/main/java/org/jabref/gui/groups/GroupTreeCellRenderer.java b/src/main/java/org/jabref/gui/groups/GroupTreeCellRenderer.java index eef25a3dc91..811b46499fc 100644 --- a/src/main/java/org/jabref/gui/groups/GroupTreeCellRenderer.java +++ b/src/main/java/org/jabref/gui/groups/GroupTreeCellRenderer.java @@ -70,7 +70,7 @@ public Component getTreeCellRendererComponent(JTree tree, Object value, boolean if (viewModel.printInItalics()) { sb.append(""); } - sb.append("Group Name"); + sb.append(viewModel.getName()); if (viewModel.printInItalics()) { sb.append(""); } diff --git a/src/main/java/org/jabref/gui/groups/GroupTreeController.java b/src/main/java/org/jabref/gui/groups/GroupTreeController.java index 529784d5d2e..1d175aaf7ef 100644 --- a/src/main/java/org/jabref/gui/groups/GroupTreeController.java +++ b/src/main/java/org/jabref/gui/groups/GroupTreeController.java @@ -1,7 +1,11 @@ package org.jabref.gui.groups; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + import javax.inject.Inject; +import javafx.beans.property.ObjectProperty; import javafx.css.PseudoClass; import javafx.event.ActionEvent; import javafx.fxml.FXML; @@ -9,6 +13,7 @@ import javafx.scene.control.Control; import javafx.scene.control.MenuItem; import javafx.scene.control.SelectionModel; +import javafx.scene.control.TextField; import javafx.scene.control.TreeItem; import javafx.scene.control.TreeTableColumn; import javafx.scene.control.TreeTableRow; @@ -24,14 +29,21 @@ import org.jabref.gui.util.ViewModelTreeTableCellFactory; import org.jabref.logic.l10n.Localization; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.controlsfx.control.textfield.CustomTextField; +import org.controlsfx.control.textfield.TextFields; import org.fxmisc.easybind.EasyBind; public class GroupTreeController extends AbstractController { + private static final Log LOGGER = LogFactory.getLog(GroupTreeController.class); + @FXML private TreeTableView groupTree; @FXML private TreeTableColumn mainColumn; @FXML private TreeTableColumn numberColumn; @FXML private TreeTableColumn disclosureNodeColumn; + @FXML private CustomTextField searchField; @Inject private StateManager stateManager; @Inject private DialogService dialogService; @@ -41,20 +53,28 @@ public void initialize() { viewModel = new GroupTreeViewModel(stateManager, dialogService); // Set-up bindings - groupTree.rootProperty().bind( - EasyBind.map(viewModel.rootGroupProperty(), - group -> new RecursiveTreeItem<>(group, GroupNodeViewModel::getChildren, GroupNodeViewModel::expandedProperty)) - ); viewModel.selectedGroupProperty().bind( EasyBind.monadic(groupTree.selectionModelProperty()) .flatMap(SelectionModel::selectedItemProperty) .selectProperty(TreeItem::valueProperty) ); + viewModel.filterTextProperty().bind(searchField.textProperty()); + searchField.textProperty().addListener((observable, oldValue, newValue) -> { + }); + + groupTree.rootProperty().bind( + EasyBind.map(viewModel.rootGroupProperty(), + group -> new RecursiveTreeItem<>( + group, + GroupNodeViewModel::getChildren, + GroupNodeViewModel::expandedProperty, + viewModel.filterPredicateProperty())) + ); // Icon and group name mainColumn.setCellValueFactory(cellData -> cellData.getValue().valueProperty()); mainColumn.setCellFactory(new ViewModelTreeTableCellFactory() - .withText(GroupNodeViewModel::getName) + .withText(GroupNodeViewModel::getDisplayName) .withIcon(GroupNodeViewModel::getIconCode) .withTooltip(GroupNodeViewModel::getDescription) ); @@ -122,6 +142,9 @@ public void initialize() { return row; }); + + // Filter text field + setupClearButtonField(searchField); } private ContextMenu createContextMenuForGroup(GroupNodeViewModel group) { @@ -137,4 +160,17 @@ private ContextMenu createContextMenuForGroup(GroupNodeViewModel group) { public void addNewGroup(ActionEvent actionEvent) { viewModel.addNewGroupToRoot(); } + + /** + * Workaround taken from https://bitbucket.org/controlsfx/controlsfx/issues/330/making-textfieldssetupclearbuttonfield + */ + private void setupClearButtonField(CustomTextField customTextField) { + try { + Method m = TextFields.class.getDeclaredMethod("setupClearButtonField", TextField.class, ObjectProperty.class); + m.setAccessible(true); + m.invoke(null, customTextField, customTextField.rightProperty()); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) { + LOGGER.error("Failed to decorate text field with clear button", ex); + } + } } diff --git a/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java b/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java index 6e7ed7e35b1..ea057cc0678 100644 --- a/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java +++ b/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java @@ -2,9 +2,13 @@ import java.util.Objects; import java.util.Optional; +import java.util.function.Predicate; +import javafx.beans.binding.Bindings; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; import org.jabref.gui.AbstractViewModel; import org.jabref.gui.DialogService; @@ -21,18 +25,23 @@ public class GroupTreeViewModel extends AbstractViewModel { private final ObjectProperty selectedGroup = new SimpleObjectProperty<>(); private final StateManager stateManager; private final DialogService dialogService; + private final ObjectProperty> filterPredicate = new SimpleObjectProperty<>(); + private final StringProperty filterText = new SimpleStringProperty(); private Optional currentDatabase; public GroupTreeViewModel(StateManager stateManager, DialogService dialogService) { this.stateManager = Objects.requireNonNull(stateManager); this.dialogService = Objects.requireNonNull(dialogService); - // Init - onActiveDatabaseChanged(stateManager.activeDatabaseProperty().getValue()); - // Register listener stateManager.activeDatabaseProperty().addListener((observable, oldValue, newValue) -> onActiveDatabaseChanged(newValue)); selectedGroup.addListener((observable, oldValue, newValue) -> onSelectedGroupChanged(newValue)); + + // Set-up bindings + filterPredicate.bind(Bindings.createObjectBinding(() -> group -> group.isMatchedBy(filterText.get()), filterText)); + + // Init + onActiveDatabaseChanged(stateManager.activeDatabaseProperty().getValue()); } public ObjectProperty rootGroupProperty() { @@ -43,6 +52,14 @@ public ObjectProperty selectedGroupProperty() { return selectedGroup; } + public ObjectProperty> filterPredicateProperty() { + return filterPredicate; + } + + public StringProperty filterTextProperty() { + return filterText; + } + /** * Gets invoked if the user selects a different group. * We need to notify the {@link StateManager} about this change so that the main table gets updated. diff --git a/src/main/java/org/jabref/gui/importer/fetcher/ACMPortalFetcher.java b/src/main/java/org/jabref/gui/importer/fetcher/ACMPortalFetcher.java index c30eb6cf0ab..c318c4f6675 100644 --- a/src/main/java/org/jabref/gui/importer/fetcher/ACMPortalFetcher.java +++ b/src/main/java/org/jabref/gui/importer/fetcher/ACMPortalFetcher.java @@ -133,7 +133,7 @@ public boolean processQueryGetPreview(String query, FetcherPreviewDialog preview try { URLDownload dl = new URLDownload(address); - String page = dl.downloadToString(Globals.prefs.getDefaultEncoding()); + String page = dl.asString(Globals.prefs.getDefaultEncoding()); int hits = getNumberOfHits(page, RESULTS_FOUND_PATTERN, ACMPortalFetcher.HITS_PATTERN); @@ -336,7 +336,7 @@ private static Optional downloadEntryBibTeX(String id, boolean downloa // get abstract if (downloadAbstract) { URLDownload dl = new URLDownload(ACMPortalFetcher.START_URL + ACMPortalFetcher.ABSTRACT_URL + id); - String page = dl.downloadToString(Globals.prefs.getDefaultEncoding()); + String page = dl.asString(Globals.prefs.getDefaultEncoding()); Matcher absM = ACMPortalFetcher.ABSTRACT_PATTERN.matcher(page); if (absM.find()) { diff --git a/src/main/java/org/jabref/gui/importer/fetcher/CiteSeerXFetcher.java b/src/main/java/org/jabref/gui/importer/fetcher/CiteSeerXFetcher.java index 2409ee9cbed..16c5983469b 100644 --- a/src/main/java/org/jabref/gui/importer/fetcher/CiteSeerXFetcher.java +++ b/src/main/java/org/jabref/gui/importer/fetcher/CiteSeerXFetcher.java @@ -115,7 +115,7 @@ private List getCitations(String query) throws IOException { } private static String getCitationsFromUrl(String urlQuery, List ids) throws IOException { - String cont = new URLDownload(urlQuery).downloadToString(Globals.prefs.getDefaultEncoding()); + String cont = new URLDownload(urlQuery).asString(Globals.prefs.getDefaultEncoding()); Matcher m = CiteSeerXFetcher.CITE_LINK_PATTERN.matcher(cont); while (m.find()) { ids.add(CiteSeerXFetcher.URL_START + m.group(1)); @@ -127,7 +127,7 @@ private static String getCitationsFromUrl(String urlQuery, List ids) thr private static BibEntry getSingleCitation(String urlString) throws IOException { - String cont = new URLDownload(urlString).downloadToString(StandardCharsets.UTF_8); + String cont = new URLDownload(urlString).asString(); // Find title, and create entry if we do. Otherwise assume we did not get an entry: Matcher m = CiteSeerXFetcher.TITLE_PATTERN.matcher(cont); diff --git a/src/main/java/org/jabref/gui/importer/fetcher/IEEEXploreFetcher.java b/src/main/java/org/jabref/gui/importer/fetcher/IEEEXploreFetcher.java index 5109975ddcf..fc5e4dd108e 100644 --- a/src/main/java/org/jabref/gui/importer/fetcher/IEEEXploreFetcher.java +++ b/src/main/java/org/jabref/gui/importer/fetcher/IEEEXploreFetcher.java @@ -4,7 +4,6 @@ import java.io.IOException; import java.net.CookieHandler; import java.net.CookieManager; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; @@ -103,14 +102,14 @@ public boolean processQuery(String query, ImportInspector dialog, OutputPrinter URLDownload dl = new URLDownload(IEEEXploreFetcher.URL_SEARCH); //add request header - dl.addParameters("Accept", "application/json"); - dl.addParameters("Content-Type", "application/json"); + dl.addHeader("Accept", "application/json"); + dl.addHeader("Content-Type", "application/json"); // set post data dl.setPostData(postData); //retrieve the search results - String page = dl.downloadToString(StandardCharsets.UTF_8); + String page = dl.asString(); //the page can be blank if the search did not work (not sure the exact conditions that lead to this, but declaring it an invalid search for now) if (page.isEmpty()) { @@ -141,7 +140,7 @@ public boolean processQuery(String query, ImportInspector dialog, OutputPrinter //fetch the raw Bibtex results from IEEEXplore String bibtexPage = new URLDownload(createBibtexQueryURL(searchResultsJson)) - .downloadToString(Globals.prefs.getDefaultEncoding()); + .asString(Globals.prefs.getDefaultEncoding()); //preprocess the result (eg. convert HTML escaped characters to latex and do other formatting not performed by BibtexParser) bibtexPage = preprocessBibtexResultsPage(bibtexPage); diff --git a/src/main/java/org/jabref/gui/net/MonitoredURLDownload.java b/src/main/java/org/jabref/gui/net/MonitoredURLDownload.java deleted file mode 100644 index 9558b39941d..00000000000 --- a/src/main/java/org/jabref/gui/net/MonitoredURLDownload.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.jabref.gui.net; - -import java.awt.Component; -import java.io.InputStream; -import java.net.URL; - -import javax.swing.ProgressMonitorInputStream; - -import org.jabref.logic.net.URLDownload; - -public class MonitoredURLDownload { - - public static URLDownload buildMonitoredDownload(final Component component, URL source) { - return new URLDownload(source) { - - @Override - protected InputStream monitorInputStream(InputStream in) { - return new ProgressMonitorInputStream(component, "Downloading " + this.getSource(), in); - } - }; - } -} diff --git a/src/main/java/org/jabref/gui/util/RecursiveTreeItem.java b/src/main/java/org/jabref/gui/util/RecursiveTreeItem.java index 2714455e99f..059e5ccb883 100644 --- a/src/main/java/org/jabref/gui/util/RecursiveTreeItem.java +++ b/src/main/java/org/jabref/gui/util/RecursiveTreeItem.java @@ -1,11 +1,17 @@ package org.jabref.gui.util; import java.util.List; +import java.util.function.Predicate; import java.util.stream.Collectors; +import javafx.beans.binding.Bindings; import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ObservableValue; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; import javafx.scene.Node; import javafx.scene.control.TreeItem; import javafx.util.Callback; @@ -17,20 +23,29 @@ public class RecursiveTreeItem extends TreeItem { private final Callback expandedProperty; private Callback> childrenFactory; + private ObjectProperty> filter = new SimpleObjectProperty<>(); + private FilteredList children; public RecursiveTreeItem(final T value, Callback> func) { - this(value, func, null); + this(value, func, null, null); } - public RecursiveTreeItem(final T value, Callback> func, Callback expandedProperty) { - this(value, (Node) null, func, expandedProperty); + public RecursiveTreeItem(final T value, Callback> func, Callback expandedProperty, ObservableValue> filter) { + this(value, null, func, expandedProperty, filter); } - public RecursiveTreeItem(final T value, Node graphic, Callback> func, Callback expandedProperty) { + public RecursiveTreeItem(final T value, Callback> func, ObservableValue> filter) { + this(value, null, func, null, filter); + } + + private RecursiveTreeItem(final T value, Node graphic, Callback> func, Callback expandedProperty, ObservableValue> filter) { super(value, graphic); this.childrenFactory = func; this.expandedProperty = expandedProperty; + if (filter != null) { + this.filter.bind(filter); + } if(value != null) { addChildrenListener(value); @@ -40,7 +55,7 @@ public RecursiveTreeItem(final T value, Node graphic, Callback{ if(newValue != null){ addChildrenListener(newValue); - bindExpandedProperty(value, expandedProperty); + bindExpandedProperty(newValue, expandedProperty); } }); } @@ -52,17 +67,14 @@ private void bindExpandedProperty(T value, Callback expanded } private void addChildrenListener(T value){ - final ObservableList children = childrenFactory.call(value); + children = new FilteredList<>(childrenFactory.call(value)); + children.predicateProperty().bind(Bindings.createObjectBinding(() -> this::showNode, filter)); - children.forEach(child -> RecursiveTreeItem.this.getChildren().add(new RecursiveTreeItem<>(child, getGraphic(), childrenFactory, expandedProperty))); + children.forEach(this::addAsChild); children.addListener((ListChangeListener) change -> { while(change.next()){ - if(change.wasAdded()){ - change.getAddedSubList().forEach(t -> RecursiveTreeItem.this.getChildren().add(new RecursiveTreeItem<>(t, getGraphic(), childrenFactory, expandedProperty))); - } - if(change.wasRemoved()){ change.getRemoved().forEach(t->{ final List> itemsToRemove = RecursiveTreeItem.this.getChildren().stream().filter(treeItem -> treeItem.getValue().equals(t)).collect(Collectors.toList()); @@ -71,7 +83,28 @@ private void addChildrenListener(T value){ }); } + if (change.wasAdded()) { + change.getAddedSubList().forEach(this::addAsChild); + } } }); } + + private boolean addAsChild(T child) { + return RecursiveTreeItem.this.getChildren().add(new RecursiveTreeItem<>(child, getGraphic(), childrenFactory, expandedProperty, filter)); + } + + private boolean showNode(T t) { + if (filter.get() == null) { + return true; + } + + if (filter.get().test(t)) { + // Node is directly matched -> so show it + return true; + } + + // Are there children (or children of children...) that are matched? If yes we also need to show this node + return childrenFactory.call(t).stream().anyMatch(this::showNode); + } } diff --git a/src/main/java/org/jabref/logic/bibtex/LatexFieldFormatter.java b/src/main/java/org/jabref/logic/bibtex/LatexFieldFormatter.java index 74c896d0ebd..36edd61616b 100644 --- a/src/main/java/org/jabref/logic/bibtex/LatexFieldFormatter.java +++ b/src/main/java/org/jabref/logic/bibtex/LatexFieldFormatter.java @@ -1,8 +1,5 @@ package org.jabref.logic.bibtex; -import java.util.ArrayList; -import java.util.List; - import org.jabref.logic.util.OS; import org.jabref.model.entry.InternalBibtexFields; import org.jabref.model.strings.StringUtil; @@ -254,26 +251,33 @@ private void putIn(String s) { } private static void checkBraces(String text) throws IllegalArgumentException { - List left = new ArrayList<>(5); - List right = new ArrayList<>(5); - int current = -1; + int left = 0; + int right = 0; // First we collect all occurrences: - while ((current = text.indexOf('{', current + 1)) != -1) { - left.add(current); - } - while ((current = text.indexOf('}', current + 1)) != -1) { - right.add(current); + for (int i = 0; i < text.length(); i++) { + char item = text.charAt(i); + + boolean charBeforeIsEscape = false; + if(i > 0 && text.charAt(i - 1) == '\\') { + charBeforeIsEscape = true; + } + + if(!charBeforeIsEscape && item == '{') { + left++; + } else if (!charBeforeIsEscape && item == '}') { + right++; + } } // Then we throw an exception if the error criteria are met. - if (!right.isEmpty() && left.isEmpty()) { + if (!(right == 0) && (left == 0)) { throw new IllegalArgumentException("'}' character ends string prematurely."); } - if (!right.isEmpty() && (right.get(0) < left.get(0))) { + if (!(right == 0) && (right < left)) { throw new IllegalArgumentException("'}' character ends string prematurely."); } - if (left.size() != right.size()) { + if (left != right) { throw new IllegalArgumentException("Braces don't match."); } diff --git a/src/main/java/org/jabref/logic/cleanup/MoveFieldCleanup.java b/src/main/java/org/jabref/logic/cleanup/MoveFieldCleanup.java index 1763b9d139b..f59fec41718 100644 --- a/src/main/java/org/jabref/logic/cleanup/MoveFieldCleanup.java +++ b/src/main/java/org/jabref/logic/cleanup/MoveFieldCleanup.java @@ -3,10 +3,10 @@ import java.util.List; import java.util.Optional; -import org.jabref.logic.util.OptionalUtil; import org.jabref.model.FieldChange; import org.jabref.model.cleanup.CleanupJob; import org.jabref.model.entry.BibEntry; +import org.jabref.model.util.OptionalUtil; /** * Moves the content of one field to another field. diff --git a/src/main/java/org/jabref/logic/importer/FulltextFetchers.java b/src/main/java/org/jabref/logic/importer/FulltextFetchers.java index d7df16071ac..a139925b594 100644 --- a/src/main/java/org/jabref/logic/importer/FulltextFetchers.java +++ b/src/main/java/org/jabref/logic/importer/FulltextFetchers.java @@ -14,6 +14,7 @@ import org.jabref.logic.importer.fetcher.IEEE; import org.jabref.logic.importer.fetcher.ScienceDirect; import org.jabref.logic.importer.fetcher.SpringerLink; +import org.jabref.logic.net.URLDownload; import org.jabref.logic.util.DOI; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.FieldName; @@ -59,7 +60,7 @@ public Optional findFullTextPDF(BibEntry entry) { try { Optional result = finder.findFullText(clonedEntry); - if (result.isPresent() && MimeTypeDetector.isPdfContentType(result.get().toString())) { + if (result.isPresent() && new URLDownload(result.get().toString()).isPdf()) { return result; } } catch (IOException | FetcherException e) { diff --git a/src/main/java/org/jabref/logic/importer/MimeTypeDetector.java b/src/main/java/org/jabref/logic/importer/MimeTypeDetector.java deleted file mode 100644 index 08f38940461..00000000000 --- a/src/main/java/org/jabref/logic/importer/MimeTypeDetector.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.jabref.logic.importer; - -import java.io.IOException; -import java.net.URL; -import java.net.URLConnection; -import java.util.Optional; - -import com.mashape.unirest.http.Unirest; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -public class MimeTypeDetector { - private static final Log LOGGER = LogFactory.getLog(MimeTypeDetector.class); - - public static boolean isPdfContentType(String url) { - Optional contentType = getMimeType(url); - - return contentType.isPresent() && contentType.get().toLowerCase().startsWith("application/pdf"); - } - - private static Optional getMimeType(String url) { - Unirest.setDefaultHeader("User-Agent", "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.4; en-US; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2"); - - // Try to use HEAD request to avoid donloading the whole file - String contentType; - try { - contentType = Unirest.head(url).asString().getHeaders().get("Content-Type").get(0); - - if (contentType != null) { - return Optional.of(contentType); - } - } catch (Exception e) { - LOGGER.debug("Error getting MIME type of URL via HEAD request", e); - } - - // Use GET request as alternative if no HEAD request is available - try { - contentType = Unirest.get(url).asString().getHeaders().get("Content-Type").get(0); - - if (contentType != null) { - return Optional.of(contentType); - } - } catch (Exception e) { - LOGGER.debug("Error getting MIME type of URL via GET request", e); - } - - // Try to resolve local URIs - try { - URLConnection connection = new URL(url).openConnection(); - - return Optional.ofNullable(connection.getContentType()); - } catch (IOException e) { - LOGGER.debug("Error trying to get MIME type of local URI", e); - } - - return Optional.empty(); - } -} diff --git a/src/main/java/org/jabref/logic/importer/fetcher/BibsonomyScraper.java b/src/main/java/org/jabref/logic/importer/fetcher/BibsonomyScraper.java index dc709afc5f6..370867fe699 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/BibsonomyScraper.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/BibsonomyScraper.java @@ -2,7 +2,6 @@ import java.io.IOException; import java.net.URL; -import java.nio.charset.StandardCharsets; import java.util.Optional; import org.jabref.logic.importer.ImportFormatPreferences; @@ -37,7 +36,7 @@ public static Optional getEntry(String entryUrl, ImportFormatPreferenc .replace("&", "%26").replace("=", "%3D"); URL url = new URL(BibsonomyScraper.BIBSONOMY_SCRAPER + cleanURL + BibsonomyScraper.BIBSONOMY_SCRAPER_POST); - String bibtex = new URLDownload(url).downloadToString(StandardCharsets.UTF_8); + String bibtex = new URLDownload(url).asString(); return BibtexParser.singleFromString(bibtex, importFormatPreferences); } catch (IOException ex) { LOGGER.warn("Could not download entry", ex); diff --git a/src/main/java/org/jabref/logic/importer/fetcher/DoiFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/DoiFetcher.java index 79bbc8d73d2..fb5553bc2f8 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/DoiFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/DoiFetcher.java @@ -2,7 +2,6 @@ import java.io.IOException; import java.net.URL; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -52,8 +51,8 @@ public Optional performSearchById(String identifier) throws FetcherExc // BibTeX data URLDownload download = new URLDownload(doiURL); - download.addParameters("Accept", "application/x-bibtex"); - String bibtexString = download.downloadToString(StandardCharsets.UTF_8); + download.addHeader("Accept", "application/x-bibtex"); + String bibtexString = download.asString(); // BibTeX entry Optional fetchedEntry = BibtexParser.singleFromString(bibtexString, preferences); diff --git a/src/main/java/org/jabref/logic/importer/fetcher/DoiResolution.java b/src/main/java/org/jabref/logic/importer/fetcher/DoiResolution.java index 086d0ece524..5bd6653edfa 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/DoiResolution.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/DoiResolution.java @@ -9,7 +9,7 @@ import java.util.Optional; import org.jabref.logic.importer.FulltextFetcher; -import org.jabref.logic.importer.MimeTypeDetector; +import org.jabref.logic.net.URLDownload; import org.jabref.logic.util.DOI; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.FieldName; @@ -61,7 +61,7 @@ public Optional findFullText(BibEntry entry) throws IOException { // Only check if pdf is included in the link or inside the text // ACM uses tokens without PDF inside the link // See https://github.com/lehner/LocalCopy for more scrape ideas - if ((href.contains("pdf") || hrefText.contains("pdf")) && MimeTypeDetector.isPdfContentType(href)) { + if ((href.contains("pdf") || hrefText.contains("pdf")) && new URLDownload(href).isPdf()) { links.add(Optional.of(new URL(href))); } } diff --git a/src/main/java/org/jabref/logic/importer/fetcher/GoogleScholar.java b/src/main/java/org/jabref/logic/importer/fetcher/GoogleScholar.java index 1984611b148..4878c61341b 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/GoogleScholar.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/GoogleScholar.java @@ -5,7 +5,6 @@ import java.net.HttpCookie; import java.net.URISyntaxException; import java.net.URL; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -142,8 +141,7 @@ public List performSearch(String query) throws FetcherException { } private void addHitsFromQuery(List entryList, String queryURL) throws IOException, FetcherException { - String content = URLDownload.createURLDownloadWithBrowserUserAgent(queryURL) - .downloadToString(StandardCharsets.UTF_8); + String content = new URLDownload(queryURL).asString(); Matcher matcher = LINK_TO_BIB_PATTERN.matcher(content); while (matcher.find()) { @@ -154,7 +152,7 @@ private void addHitsFromQuery(List entryList, String queryURL) throws } private BibEntry downloadEntry(String link) throws IOException, FetcherException { - String downloadedContent = URLDownload.createURLDownloadWithBrowserUserAgent(link).downloadToString(StandardCharsets.UTF_8); + String downloadedContent = new URLDownload(link).asString(); BibtexParser parser = new BibtexParser(importFormatPreferences); ParserResult result = parser.parse(new StringReader(downloadedContent)); if ((result == null) || (result.getDatabase() == null)) { @@ -173,7 +171,7 @@ private BibEntry downloadEntry(String link) throws IOException, FetcherException private void obtainAndModifyCookie() throws FetcherException { try { - URLDownload downloader = URLDownload.createURLDownloadWithBrowserUserAgent("https://scholar.google.com"); + URLDownload downloader = new URLDownload("https://scholar.google.com"); List cookies = downloader.getCookieFromUrl(); for (HttpCookie cookie : cookies) { // append "CF=4" which represents "Citation format bibtex" diff --git a/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java b/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java index e64032d243e..4d9d1ae79f1 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java @@ -2,7 +2,6 @@ import java.io.IOException; import java.net.URL; -import java.nio.charset.StandardCharsets; import java.util.Objects; import java.util.Optional; import java.util.regex.Matcher; @@ -53,8 +52,7 @@ public Optional findFullText(BibEntry entry) throws IOException { Optional doi = entry.getField(FieldName.DOI).flatMap(DOI::build); if (doi.isPresent() && doi.get().getDOI().startsWith(IEEE_DOI) && doi.get().getURI().isPresent()) { // Download the HTML page from IEEE - String resolvedDOIPage = new URLDownload(doi.get().getURI().get().toURL()) - .downloadToString(StandardCharsets.UTF_8); + String resolvedDOIPage = new URLDownload(doi.get().getURI().get().toURL()).asString(); // Try to find the link Matcher matcher = STAMP_PATTERN.matcher(resolvedDOIPage); if (matcher.find()) { @@ -70,7 +68,7 @@ public Optional findFullText(BibEntry entry) throws IOException { } // Download the HTML page containing a frame with the PDF - String framePage = new URLDownload(BASE_URL + stampString).downloadToString(StandardCharsets.UTF_8); + String framePage = new URLDownload(BASE_URL + stampString).asString(); // Try to find the direct PDF link Matcher matcher = PDF_PATTERN.matcher(framePage); if (matcher.find()) { diff --git a/src/main/java/org/jabref/logic/importer/fetcher/MrDLibFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/MrDLibFetcher.java index 9d3bdf4380d..bfe46bae381 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/MrDLibFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/MrDLibFetcher.java @@ -8,7 +8,6 @@ import java.io.StringReader; import java.net.URI; import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -87,8 +86,8 @@ public List performSearch(BibEntry entry) throws FetcherException { private String makeServerRequest(String queryByTitle) throws FetcherException { try { URLDownload urlDownload = new URLDownload(constructQuery(queryByTitle)); - urlDownload.fixSSLVerification(); - String response = urlDownload.downloadToString(StandardCharsets.UTF_8); + urlDownload.bypassSSLVerification(); + String response = urlDownload.asString(); //Conversion of < and > response = response.replaceAll(">", ">"); diff --git a/src/main/java/org/jabref/logic/importer/fetcher/zbMATH.java b/src/main/java/org/jabref/logic/importer/fetcher/zbMATH.java index 244340d80a1..f4aca185790 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/zbMATH.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/zbMATH.java @@ -3,16 +3,8 @@ import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; -import java.security.SecureRandom; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; import java.util.Objects; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; - import org.jabref.logic.cleanup.MoveFieldCleanup; import org.jabref.logic.formatter.bibtexfields.RemoveBracesFormatter; import org.jabref.logic.importer.FetcherException; @@ -20,6 +12,7 @@ import org.jabref.logic.importer.Parser; import org.jabref.logic.importer.SearchBasedParserFetcher; import org.jabref.logic.importer.fileformat.BibtexParser; +import org.jabref.logic.net.URLDownload; import org.jabref.model.cleanup.FieldFormatterCleanup; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.FieldName; @@ -32,7 +25,6 @@ * Fetches data from the Zentralblatt Math (https://www.zbmath.org/) */ public class zbMATH implements SearchBasedParserFetcher { - private static final Log LOGGER = LogFactory.getLog(zbMATH.class); private final ImportFormatPreferences preferences; @@ -65,56 +57,11 @@ public URL getURLForQuery(String query) throws URISyntaxException, MalformedURLE uriBuilder.addParameter("start", "0"); // start index uriBuilder.addParameter("count", "200"); // should return up to 200 items (instead of default 100) - fixSSLVerification(); + URLDownload.bypassSSLVerification(); return uriBuilder.build().toURL(); } - /** - * Older java VMs does not automatically trust the zbMATH certificate. In this case the following exception is thrown: - * sun.security.validator.ValidatorException: PKIX path building failed: - * sun.security.provider.certpath.SunCertPathBuilderException: unable to find - * valid certification path to requested target - * JM > 8u101 may trust the certificate by default according to http://stackoverflow.com/a/34111150/873661 - * - * We will fix this issue by accepting all (!) certificates. This is ugly; but as JabRef does not rely on - * security-relevant information this is kind of OK (no, actually it is not...). - * - * Taken from http://stackoverflow.com/a/6055903/873661 - */ - private void fixSSLVerification() { - - LOGGER.warn("Fix SSL exception by accepting ALL certificates"); - - // Create a trust manager that does not validate certificate chains - TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() { - - @Override - public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { - - } - - @Override - public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { - - } - - @Override - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[0]; - } - } }; - - // Install the all-trusting trust manager - try { - SSLContext sc = SSLContext.getInstance("TLS"); - sc.init(null, trustAllCerts, new SecureRandom()); - HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); - } catch (Exception e) { - LOGGER.error("SSL problem", e); - } - } - @Override public Parser getParser() { return new BibtexParser(preferences); diff --git a/src/main/java/org/jabref/logic/integrity/FieldChecker.java b/src/main/java/org/jabref/logic/integrity/FieldChecker.java index b0856bdf12a..fe4301e9a0f 100644 --- a/src/main/java/org/jabref/logic/integrity/FieldChecker.java +++ b/src/main/java/org/jabref/logic/integrity/FieldChecker.java @@ -5,8 +5,8 @@ import java.util.Objects; import java.util.Optional; -import org.jabref.logic.util.OptionalUtil; import org.jabref.model.entry.BibEntry; +import org.jabref.model.util.OptionalUtil; public class FieldChecker implements IntegrityCheck.Checker { protected final String field; diff --git a/src/main/java/org/jabref/logic/net/URLDownload.java b/src/main/java/org/jabref/logic/net/URLDownload.java index 09429d320cf..7313fa3a485 100644 --- a/src/main/java/org/jabref/logic/net/URLDownload.java +++ b/src/main/java/org/jabref/logic/net/URLDownload.java @@ -3,7 +3,6 @@ import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.DataOutputStream; -import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -20,6 +19,7 @@ import java.net.URL; import java.net.URLConnection; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; @@ -29,6 +29,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; @@ -37,6 +38,7 @@ import org.jabref.logic.util.io.FileUtil; +import com.mashape.unirest.http.Unirest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -45,37 +47,28 @@ *

* Example: * URLDownload dl = new URLDownload(URL); - * String content = dl.downloadToString(ENCODING); - * dl.downloadToFile(FILE); // available in FILE - * String contentType = dl.determineMimeType(); + * String content = dl.asString(ENCODING); + * dl.toFile(Path); // available in FILE + * String contentType = dl.getMimeType(); * * Each call to a public method creates a new HTTP connection. Nothing is cached. - * - * @author Erik Putrycz erik.putrycz-at-nrc-cnrc.gc.ca - * @author Simon Harrer */ public class URLDownload { private static final Log LOGGER = LogFactory.getLog(URLDownload.class); - private static final String USER_AGENT= "JabRef"; + private static final String USER_AGENT = "Mozilla/5.0 (Windows NT 5.1; rv:31.0) Gecko/20100101 Firefox/31.0"; private final URL source; private final Map parameters = new HashMap<>(); private String postData = ""; - public static URLDownload createURLDownloadWithBrowserUserAgent(String address) throws MalformedURLException { - URLDownload downloader = new URLDownload(address); - downloader.addParameters("User-Agent", "Mozilla/5.0 (Windows NT 5.1; rv:31.0) Gecko/20100101 Firefox/31.0"); - return downloader; - } - /** - * @param address the URL to download from - * @throws MalformedURLException if no protocol is specified in the address, or an unknown protocol is found + * @param source the URL to download from + * @throws MalformedURLException if no protocol is specified in the source, or an unknown protocol is found */ - public URLDownload(String address) throws MalformedURLException { - this(new URL(address)); + public URLDownload(String source) throws MalformedURLException { + this(new URL(source)); } /** @@ -83,81 +76,80 @@ public URLDownload(String address) throws MalformedURLException { */ public URLDownload(URL source) { this.source = source; - addParameters("User-Agent", USER_AGENT); + this.addHeader("User-Agent", URLDownload.USER_AGENT); } - public URL getSource() { - return source; - } + public String getMimeType() throws IOException { + Unirest.setDefaultHeader("User-Agent", "Mozilla/5.0 (Windows; U; WindowsNT 5.1; en-US; rv1.8.1.6) Gecko/20070725 Firefox/2.0.0.6"); + String contentType; + // Try to use HEAD request to avoid downloading the whole file + try { + contentType = Unirest.head(source.toString()).asString().getHeaders().get("Content-Type").get(0); + if (contentType != null && !contentType.isEmpty()) { + return contentType; + } + } catch (Exception e) { + LOGGER.debug("Error getting MIME type of URL via HEAD request", e); + } - public String determineMimeType() throws IOException { - // this does not cause a real performance issue as the underlying HTTP/TCP connection is reused - URLConnection urlConnection = openConnection(); + // Use GET request as alternative if no HEAD request is available try { - return urlConnection.getContentType(); - } finally { - try { - urlConnection.getInputStream().close(); - } catch (IOException ignored) { - // Ignored + contentType = Unirest.get(source.toString()).asString().getHeaders().get("Content-Type").get(0); + if (contentType != null && !contentType.isEmpty()) { + return contentType; } + } catch (Exception e) { + LOGGER.debug("Error getting MIME type of URL via GET request", e); } - } - public void addParameters(String key, String value) { - parameters.put(key, value); - } + // Try to resolve local URIs + try { + URLConnection connection = new URL(source.toString()).openConnection(); - public void setPostData(String postData) { - if (postData != null) { - this.postData = postData; + contentType = connection.getContentType(); + if (contentType != null && !contentType.isEmpty()) { + return contentType; + } + } catch (IOException e) { + LOGGER.debug("Error trying to get MIME type of local URI", e); } + + return ""; } - private URLConnection openConnection() throws IOException { - URLConnection connection = source.openConnection(); - for (Map.Entry entry : parameters.entrySet()) { - connection.setRequestProperty(entry.getKey(), entry.getValue()); - } - if (!postData.isEmpty()) { - connection.setDoOutput(true); - try (DataOutputStream wr = new DataOutputStream(connection.getOutputStream())) { - wr.writeBytes(postData); - } + public boolean isMimeType(String type) throws IOException { + String mime = getMimeType(); + if (mime.isEmpty()) { + return false; } - if (connection instanceof HttpURLConnection) { - // normally, 3xx is redirect - int status = ((HttpURLConnection) connection).getResponseCode(); - if (status != HttpURLConnection.HTTP_OK) { - if ((status == HttpURLConnection.HTTP_MOVED_TEMP) - || (status == HttpURLConnection.HTTP_MOVED_PERM) - || (status == HttpURLConnection.HTTP_SEE_OTHER)) { - // get redirect url from "location" header field - String newUrl = connection.getHeaderField("Location"); - // open the new connnection again - connection = new URLDownload(newUrl).openConnection(); - } - } - } + return mime.startsWith(type); + } - // this does network i/o: GET + read returned headers - connection.connect(); + public boolean isPdf() throws IOException { + return isMimeType("application/pdf"); + } - return connection; + public void addHeader(String key, String value) { + this.parameters.put(key, value); + } + + public void setPostData(String postData) { + if (postData != null) { + this.postData = postData; + } } /** + * Downloads the web resource to a String. * + * @param encoding the desired String encoding * @return the downloaded string - * @throws IOException */ - - public String downloadToString(Charset encoding) throws IOException { - - try (InputStream input = new BufferedInputStream(openConnection().getInputStream()); + public String asString(Charset encoding) throws IOException { + try (InputStream input = new BufferedInputStream(this.openConnection().getInputStream()); Writer output = new StringWriter()) { copy(input, output, encoding); return output.toString(); @@ -167,47 +159,39 @@ public String downloadToString(Charset encoding) throws IOException { } } + /** + * Downloads the web resource to a String. + * Uses UTF-8 as encoding. + * + * @return the downloaded string + */ + public String asString() throws IOException { + return asString(StandardCharsets.UTF_8); + } + public List getCookieFromUrl() throws IOException { CookieManager cookieManager = new CookieManager(); CookieHandler.setDefault(cookieManager); cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL); - URLConnection con = openConnection(); + URLConnection con = this.openConnection(); con.getHeaderFields(); // must be read to store the cookie try { - return cookieManager.getCookieStore().get(source.toURI()); + return cookieManager.getCookieStore().get(this.source.toURI()); } catch (URISyntaxException e) { LOGGER.error("Unable to convert download URL to URI", e); return Collections.emptyList(); } - - } - - private void copy(InputStream in, Writer out, Charset encoding) throws IOException { - InputStream monitoredInputStream = monitorInputStream(in); - Reader r = new InputStreamReader(monitoredInputStream, encoding); - try (BufferedReader read = new BufferedReader(r)) { - - String line; - while ((line = read.readLine()) != null) { - out.write(line); - out.write("\n"); - } - } } /** - * @deprecated use {@link #downloadToFile(Path)} + * Downloads the web resource to a file. + * + * @param destination the destination file path. */ - @Deprecated - public void downloadToFile(File destination) throws IOException { - downloadToFile(destination.toPath()); - } - - public void downloadToFile(Path destination) throws IOException { - - try (InputStream input = monitorInputStream(new BufferedInputStream(openConnection().getInputStream()))) { + public void toFile(Path destination) throws IOException { + try (InputStream input = new BufferedInputStream(this.openConnection().getInputStream())) { Files.copy(input, destination, StandardCopyOption.REPLACE_EXISTING); } catch (IOException e) { LOGGER.warn("Could not copy input", e); @@ -218,9 +202,9 @@ public void downloadToFile(Path destination) throws IOException { /** * Downloads the web resource to a temporary file. * - * @return the path to the downloaded file. + * @return the path of the temporary file. */ - public Path downloadToTemporaryFile() throws IOException { + public Path toTemporaryFile() throws IOException { // Determine file name and extension from source url String sourcePath = source.getPath(); @@ -231,53 +215,102 @@ public Path downloadToTemporaryFile() throws IOException { // Create temporary file and download to it Path file = Files.createTempFile(fileName, extension); - downloadToFile(file); - return file; - } + toFile(file); - protected InputStream monitorInputStream(InputStream in) { - return in; + return file; } @Override public String toString() { - return "URLDownload{" + "source=" + source + '}'; + return "URLDownload{" + "source=" + this.source + '}'; } - public void fixSSLVerification() { + /** + * Older java VMs does not automatically trust the zbMATH certificate. In this case the following exception is thrown: + * sun.security.validator.ValidatorException: PKIX path building failed: + * sun.security.provider.certpath.SunCertPathBuilderException: unable to find + * valid certification path to requested target + * JM > 8u101 may trust the certificate by default according to http://stackoverflow.com/a/34111150/873661 + * + * We will fix this issue by accepting all (!) certificates. This is ugly; but as JabRef does not rely on + * security-relevant information this is kind of OK (no, actually it is not...). + * + * Taken from http://stackoverflow.com/a/6055903/873661 + */ + public static void bypassSSLVerification() { + LOGGER.warn("Fix SSL exceptions by accepting ALL certificates"); // Create a trust manager that does not validate certificate chains - TrustManager[] trustAllCerts = new TrustManager[] {new X509TrustManager() { - + TrustManager[] trustAllCerts = { new X509TrustManager() { @Override - public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) - throws java.security.cert.CertificateException { - // TODO Auto-generated method stub - + public void checkClientTrusted(X509Certificate[] chain, String authType) { } @Override - public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) - throws java.security.cert.CertificateException { - // TODO Auto-generated method stub - + public void checkServerTrusted(X509Certificate[] chain, String authType) { } @Override - public java.security.cert.X509Certificate[] getAcceptedIssuers() { - // TODO Auto-generated method stub + public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } - }}; // Install the all-trusting trust manager try { - SSLContext sc = SSLContext.getInstance("TLS"); - sc.init(null, trustAllCerts, new SecureRandom()); - HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); + SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, trustAllCerts, new SecureRandom()); + HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory()); } catch (Exception e) { - LOGGER.error("SSL problem", e); + LOGGER.error("A problem occurred when bypassing SSL verification", e); } } + + private void copy(InputStream in, Writer out, Charset encoding) throws IOException { + InputStream monitoredInputStream = in; + Reader r = new InputStreamReader(monitoredInputStream, encoding); + try (BufferedReader read = new BufferedReader(r)) { + + String line; + while ((line = read.readLine()) != null) { + out.write(line); + out.write("\n"); + } + } + } + + private URLConnection openConnection() throws IOException { + URLConnection connection = this.source.openConnection(); + for (Entry entry : this.parameters.entrySet()) { + connection.setRequestProperty(entry.getKey(), entry.getValue()); + } + if (!this.postData.isEmpty()) { + connection.setDoOutput(true); + try (DataOutputStream wr = new DataOutputStream(connection.getOutputStream())) { + wr.writeBytes(this.postData); + } + + } + + if (connection instanceof HttpURLConnection) { + // normally, 3xx is redirect + int status = ((HttpURLConnection) connection).getResponseCode(); + if (status != HttpURLConnection.HTTP_OK) { + if (status == HttpURLConnection.HTTP_MOVED_TEMP + || status == HttpURLConnection.HTTP_MOVED_PERM + || status == HttpURLConnection.HTTP_SEE_OTHER) { + // get redirect url from "location" header field + String newUrl = connection.getHeaderField("Location"); + // open the new connnection again + connection = new URLDownload(newUrl).openConnection(); + } + } + } + + // this does network i/o: GET + read returned headers + connection.connect(); + + return connection; + } + } diff --git a/src/main/java/org/jabref/logic/util/OptionalUtil.java b/src/main/java/org/jabref/logic/util/OptionalUtil.java deleted file mode 100644 index ba0d00ce0aa..00000000000 --- a/src/main/java/org/jabref/logic/util/OptionalUtil.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.jabref.logic.util; - -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class OptionalUtil { - - public static List toList(Optional value) { - if (value.isPresent()) { - return Collections.singletonList(value.get()); - } else { - return Collections.emptyList(); - } - } - - @SafeVarargs - public static List toList(Optional... values) { - return Stream.of(values).flatMap(optional -> toList(optional).stream()).collect(Collectors.toList()); - } -} diff --git a/src/main/java/org/jabref/model/database/BibDatabase.java b/src/main/java/org/jabref/model/database/BibDatabase.java index 59fae7fb306..ff9a1593141 100644 --- a/src/main/java/org/jabref/model/database/BibDatabase.java +++ b/src/main/java/org/jabref/model/database/BibDatabase.java @@ -4,7 +4,6 @@ import java.security.SecureRandom; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; @@ -17,6 +16,9 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + import org.jabref.model.database.event.EntryAddedEvent; import org.jabref.model.database.event.EntryRemovedEvent; import org.jabref.model.entry.BibEntry; @@ -39,37 +41,47 @@ */ public class BibDatabase { private static final Log LOGGER = LogFactory.getLog(BibDatabase.class); - + private static final Pattern RESOLVE_CONTENT_PATTERN = Pattern.compile(".*#[^#]+#.*"); /** * State attributes */ - private final List entries = Collections.synchronizedList(new ArrayList<>()); - - private String preamble; - // All file contents below the last entry in the file - private String epilog = ""; + private final ObservableList entries = FXCollections.synchronizedObservableList(FXCollections.observableArrayList()); private final Map bibtexStrings = new ConcurrentHashMap<>(); - /** * this is kept in sync with the database (upon adding/removing an entry, it is updated as well) */ private final DuplicationChecker duplicationChecker = new DuplicationChecker(); - /** * contains all entry.getID() of the current database */ private final Set internalIDs = new HashSet<>(); - private final EventBus eventBus = new EventBus(); - + private String preamble; + // All file contents below the last entry in the file + private String epilog = ""; private String sharedDatabaseID; - public BibDatabase() { this.eventBus.register(duplicationChecker); this.registerListener(new KeyChangeListener(this)); } + /** + * @param toResolve maybenull The text to resolve. + * @param database maybenull The database to use for resolving the text. + * @return The resolved text or the original text if either the text or the database are null + * @deprecated use {@link BibDatabase#resolveForStrings(String)} + * + * Returns a text with references resolved according to an optionally given database. + */ + @Deprecated + public static String getText(String toResolve, BibDatabase database) { + if ((toResolve != null) && (database != null)) { + return database.resolveForStrings(toResolve); + } + return toResolve; + } + /** * Returns the number of entries. */ @@ -99,8 +111,8 @@ public boolean containsEntryWithId(String id) { return internalIDs.contains(id); } - public List getEntries() { - return Collections.unmodifiableList(entries); + public ObservableList getEntries() { + return FXCollections.unmodifiableObservableList(entries); } /** @@ -222,13 +234,6 @@ public synchronized void removeEntry(BibEntry toBeDeleted, EntryEventSource even } } - /** - * Sets the database's preamble. - */ - public synchronized void setPreamble(String preamble) { - this.preamble = preamble; - } - /** * Returns the database's preamble. * If the preamble text consists only of whitespace, then also an empty optional is returned. @@ -241,6 +246,13 @@ public synchronized Optional getPreamble() { } } + /** + * Sets the database's preamble. + */ + public synchronized void setPreamble(String preamble) { + this.preamble = preamble; + } + /** * Inserts a Bibtex String. */ @@ -467,10 +479,6 @@ private String resolveString(String label, Set usedIds, Set allU } } - - private static final Pattern RESOLVE_CONTENT_PATTERN = Pattern.compile(".*#[^#]+#.*"); - - private String resolveContent(String result, Set usedIds, Set allUsedIds) { String res = result; if (RESOLVE_CONTENT_PATTERN.matcher(res).matches()) { @@ -519,31 +527,14 @@ private String resolveContent(String result, Set usedIds, Set al return res; } - /** - * @deprecated use {@link BibDatabase#resolveForStrings(String)} - * - * Returns a text with references resolved according to an optionally given database. - * - * @param toResolve maybenull The text to resolve. - * @param database maybenull The database to use for resolving the text. - * @return The resolved text or the original text if either the text or the database are null - */ - @Deprecated - public static String getText(String toResolve, BibDatabase database) { - if ((toResolve != null) && (database != null)) { - return database.resolveForStrings(toResolve); - } - return toResolve; + public String getEpilog() { + return epilog; } public void setEpilog(String epilog) { this.epilog = epilog; } - public String getEpilog() { - return epilog; - } - /** * Registers an listener object (subscriber) to the internal event bus. * The following events are posted: @@ -584,14 +575,14 @@ public Optional getSharedDatabaseID() { return Optional.ofNullable(this.sharedDatabaseID); } - public boolean isShared() { - return getSharedDatabaseID().isPresent(); - } - public void setSharedDatabaseID(String sharedDatabaseID) { this.sharedDatabaseID = sharedDatabaseID; } + public boolean isShared() { + return getSharedDatabaseID().isPresent(); + } + public void clearSharedDatabaseID() { this.sharedDatabaseID = null; } diff --git a/src/main/java/org/jabref/model/groups/AutomaticGroup.java b/src/main/java/org/jabref/model/groups/AutomaticGroup.java new file mode 100644 index 00000000000..62d619fa5cf --- /dev/null +++ b/src/main/java/org/jabref/model/groups/AutomaticGroup.java @@ -0,0 +1,23 @@ +package org.jabref.model.groups; + +import java.util.Set; + +import org.jabref.model.entry.BibEntry; + +public abstract class AutomaticGroup extends AbstractGroup { + public AutomaticGroup(String name, GroupHierarchyType context) { + super(name, context); + } + + @Override + public boolean contains(BibEntry entry) { + return false; + } + + @Override + public boolean isDynamic() { + return false; + } + + public abstract Set createSubgroups(BibEntry entry); +} diff --git a/src/main/java/org/jabref/model/groups/AutomaticKeywordGroup.java b/src/main/java/org/jabref/model/groups/AutomaticKeywordGroup.java new file mode 100644 index 00000000000..48641d24915 --- /dev/null +++ b/src/main/java/org/jabref/model/groups/AutomaticKeywordGroup.java @@ -0,0 +1,44 @@ +package org.jabref.model.groups; + +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.KeywordList; +import org.jabref.model.util.OptionalUtil; + +public class AutomaticKeywordGroup extends AutomaticGroup { + + private Character keywordSeperator; + private String field; + + public AutomaticKeywordGroup(String name, GroupHierarchyType context, String field, Character keywordSeperator) { + super(name, context); + this.field = field; + this.keywordSeperator = keywordSeperator; + } + + public Character getKeywordSeperator() { + return keywordSeperator; + } + + public String getField() { + return field; + } + + @Override + public AbstractGroup deepCopy() { + return new AutomaticKeywordGroup(this.name, this.context, field, this.keywordSeperator); + } + + @Override + public Set createSubgroups(BibEntry entry) { + Optional keywordList = entry.getLatexFreeField(field) + .map(fieldValue -> KeywordList.parse(fieldValue, keywordSeperator)); + return OptionalUtil.flatMap(keywordList, KeywordList::toStringList) + .map(keyword -> new WordKeywordGroup(keyword, GroupHierarchyType.INDEPENDENT, field, keyword, true, keywordSeperator, true)) + .map(GroupTreeNode::new) + .collect(Collectors.toSet()); + } +} diff --git a/src/main/java/org/jabref/model/groups/AutomaticPersonsGroup.java b/src/main/java/org/jabref/model/groups/AutomaticPersonsGroup.java new file mode 100644 index 00000000000..5e4be74b20a --- /dev/null +++ b/src/main/java/org/jabref/model/groups/AutomaticPersonsGroup.java @@ -0,0 +1,43 @@ +package org.jabref.model.groups; + +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import org.jabref.model.entry.Author; +import org.jabref.model.entry.AuthorList; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.util.OptionalUtil; + +public class AutomaticPersonsGroup extends AutomaticGroup { + + private String field; + + public AutomaticPersonsGroup(String name, GroupHierarchyType context, String field) { + super(name, context); + this.field = field; + } + + @Override + public AbstractGroup deepCopy() { + return new AutomaticPersonsGroup(this.name, this.context, this.field); + } + + @Override + public Set createSubgroups(BibEntry entry) { + Optional authorList = entry.getLatexFreeField(field) + .map(AuthorList::parse); + return OptionalUtil.flatMap(authorList, AuthorList::getAuthors) + .map(Author::getLast) + .filter(Optional::isPresent) + .map(Optional::get) + .filter(lastName -> !lastName.isEmpty()) + .map(lastName -> new WordKeywordGroup(lastName, GroupHierarchyType.INDEPENDENT, field, lastName, true, ' ', true)) + .map(GroupTreeNode::new) + .collect(Collectors.toSet()); + } + + public String getField() { + return field; + } +} diff --git a/src/main/java/org/jabref/model/util/OptionalUtil.java b/src/main/java/org/jabref/model/util/OptionalUtil.java new file mode 100644 index 00000000000..d2b482b80ee --- /dev/null +++ b/src/main/java/org/jabref/model/util/OptionalUtil.java @@ -0,0 +1,44 @@ +package org.jabref.model.util; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class OptionalUtil { + + public static List toList(Optional value) { + if (value.isPresent()) { + return Collections.singletonList(value.get()); + } else { + return Collections.emptyList(); + } + } + + /** + * No longer needed in Java 9 where {@code Optional.stream()} is added. + */ + public static Stream toStream(Optional value) { + if (value.isPresent()) { + return Stream.of(value.get()); + } else { + return Stream.empty(); + } + } + + @SafeVarargs + public static List toList(Optional... values) { + return Stream.of(values).flatMap(optional -> toList(optional).stream()).collect(Collectors.toList()); + } + + public static Stream flatMapFromStream(Optional value, Function> mapper) { + return toStream(value).flatMap(mapper); + } + + public static Stream flatMap(Optional value, Function> mapper) { + return toStream(value).flatMap(element -> mapper.apply(element).stream()); + } +} diff --git a/src/main/java/org/jabref/pdfimport/PdfImporter.java b/src/main/java/org/jabref/pdfimport/PdfImporter.java index 0e6d67f4ec8..e7cf29f8d6e 100644 --- a/src/main/java/org/jabref/pdfimport/PdfImporter.java +++ b/src/main/java/org/jabref/pdfimport/PdfImporter.java @@ -64,30 +64,25 @@ public PdfImporter(JabRefFrame frame, BasePanel panel, MainTable entryTable, int this.dropRow = dropRow; } - public class ImportPdfFilesResult { private final List noPdfFiles; private final List entries; - public ImportPdfFilesResult(List noPdfFiles, List entries) { this.noPdfFiles = noPdfFiles; this.entries = entries; } - public List getNoPdfFiles() { return noPdfFiles; } - public List getEntries() { return entries; } } - /** * * Imports the PDF files given by fileNames @@ -128,7 +123,6 @@ private List importPdfFilesInternal(List fileNames) { boolean neverShow = Globals.prefs.getBoolean(JabRefPreferences.IMPORT_ALWAYSUSE); int globalChoice = Globals.prefs.getInt(JabRefPreferences.IMPORT_DEFAULT_PDF_IMPORT_STYLE); - List res = new ArrayList<>(); for (String fileName : fileNames) { @@ -156,7 +150,11 @@ private List importPdfFilesInternal(List fileNames) { break; case ImportDialog.ONLYATTACH: DroppedFileHandler dfh = new DroppedFileHandler(frame, panel); - dfh.linkPdfToEntry(fileName, entryTable, dropRow); + if (dropRow >= 0) { + dfh.linkPdfToEntry(fileName, entryTable, dropRow); + } else { + dfh.linkPdfToEntry(fileName, entryTable, entryTable.getSelectedRow()); + } break; default: break; @@ -236,7 +234,8 @@ private void doContentImport(String fileName, List res) { panel.getDatabase().insertEntry(entry); panel.markBaseChanged(); BibtexKeyPatternUtil.makeAndSetLabel(panel.getBibDatabaseContext().getMetaData() - .getCiteKeyPattern(Globals.prefs.getBibtexKeyPatternPreferences().getKeyPattern()), panel.getDatabase(), entry, + .getCiteKeyPattern(Globals.prefs.getBibtexKeyPatternPreferences().getKeyPattern()), panel.getDatabase(), + entry, Globals.prefs.getBibtexKeyPatternPreferences()); DroppedFileHandler dfh = new DroppedFileHandler(frame, panel); dfh.linkPdfToEntry(fileName, entry); diff --git a/src/main/resources/l10n/JabRef_da.properties b/src/main/resources/l10n/JabRef_da.properties index 97788a7d8d2..364c671539e 100644 --- a/src/main/resources/l10n/JabRef_da.properties +++ b/src/main/resources/l10n/JabRef_da.properties @@ -133,10 +133,6 @@ Autolink_only_files_that_match_the_BibTeX_key=Autolink_kun_filer_med_navn_som_sv Automatically_create_groups=Generer_grupper_automatisk -Automatically_create_groups_for_library.=Generer_grupper_for_libraryn. - -Automatically_created_groups=Genererede_grupper_automatisk - Automatically_remove_exact_duplicates=Fjern_eksakte_dubletter_automatisk Allow_overwriting_existing_links.=Tillad_overskrivning_af_eksisterende_links. @@ -209,8 +205,6 @@ Changed_look_and_feel_settings=Ændrede_brugerfladeindstillinger Changed_preamble=Ændrede_præambel -Characters_to_ignore=Ignorer_følgende_tegn - Check_existing_file_links=Tjek_eksisterende_fil-links Check_links=Tjek_eksterne_links @@ -287,8 +281,6 @@ Could_not_run_the_'vim'_program.=Kunne_ikke_køre_'vim'-programmet Could_not_save_file.=Kunne_ikke_gemme_fil. Character_encoding_'%0'_is_not_supported.=Tegnkodingen_'%0'_er_ikke_understøttet. -Created_groups.=Oprettede_grupper. - crossreferenced_entries_included=refererede_poster_inkluderet Current_content=Nuværende_indhold @@ -1470,7 +1462,6 @@ Metadata_change=Metadata-ændring Changes_have_been_made_to_the_following_metadata_elements=Der_er_ændringer_i_følgende_metadata-elementer Generate_groups_for_author_last_names=Generer_grupper_for_forfatteres_efternavne -Generate_groups_for_editor_last_names=Generer_grupper_for_redaktørers_efternavne Generate_groups_from_keywords_in_a_BibTeX_field=Generer_grupper_ud_fra_nøgleord_i_et_BibTeX-felt Enforce_legal_characters_in_BibTeX_keys=Håndhæv_tilladte_tegn_i_BibTeX-nøgler diff --git a/src/main/resources/l10n/JabRef_de.properties b/src/main/resources/l10n/JabRef_de.properties index 0d0eb9e684d..753a6021e99 100644 --- a/src/main/resources/l10n/JabRef_de.properties +++ b/src/main/resources/l10n/JabRef_de.properties @@ -133,10 +133,6 @@ Autolink_only_files_that_match_the_BibTeX_key=Nur_Dateien_verlinken,_deren_Namen Automatically_create_groups=Gruppen_automatisch_erstellen -Automatically_create_groups_for_library.=Automatisch_Gruppen_für_die_Datenbank_anlegen. - -Automatically_created_groups=Automatisch_erzeugte_Gruppen - Automatically_remove_exact_duplicates=Exakte_Duplikate_automatisch_löschen Allow_overwriting_existing_links.=Vorhandene_Links_überschreiben. @@ -209,8 +205,6 @@ Changed_look_and_feel_settings="Look_and_Feel"-Einstellungen_geändert Changed_preamble=Präambel_geändert -Characters_to_ignore=Folgende_Zeichen_ignorieren - Check_existing_file_links=Existierende_Datei-Links_überprüfen Check_links=Links_überprüfen @@ -287,8 +281,6 @@ Could_not_run_the_'vim'_program.=Das_Programm_'vim'_konnte_nicht_gestartet_werde Could_not_save_file.=Datei_konnte_nicht_gespeichert_werden. Character_encoding_'%0'_is_not_supported.=Die_Zeichenkodierung_'%0'_wird_nicht_unterstützt. -Created_groups.=Gruppen_erstellt. - crossreferenced_entries_included=Inklusive_querverwiesenen_Einträgen Current_content=Aktueller_Inhalt @@ -1470,7 +1462,6 @@ Metadata_change=Metadaten-Änderung Changes_have_been_made_to_the_following_metadata_elements=An_den_folgenden_Metadaten_wurden_Änderungen_vorgenommen Generate_groups_for_author_last_names=Erstelle_Gruppen_für_Nachnamen_der_Autoren -Generate_groups_for_editor_last_names=Erstelle_Gruppen_für_Nachnamen_der_Herausgeber Generate_groups_from_keywords_in_a_BibTeX_field=Erstelle_Gruppen_aus_den_Stichwörtern_eines_BibTeX-Feldes Enforce_legal_characters_in_BibTeX_keys=Erzwinge_erlaubte_Zeichen_in_BibTeX-Keys diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 046c754df0d..3d1ce55bf67 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -133,10 +133,6 @@ Autolink_only_files_that_match_the_BibTeX_key=Autolink_only_files_that_match_the Automatically_create_groups=Automatically_create_groups -Automatically_create_groups_for_library.=Automatically_create_groups_for_library. - -Automatically_created_groups=Automatically_created_groups - Automatically_remove_exact_duplicates=Automatically_remove_exact_duplicates Allow_overwriting_existing_links.=Allow_overwriting_existing_links. @@ -209,8 +205,6 @@ Changed_look_and_feel_settings=Changed_look_and_feel_settings Changed_preamble=Changed_preamble -Characters_to_ignore=Characters_to_ignore - Check_existing_file_links=Check_existing_file_links Check_links=Check_links @@ -287,8 +281,6 @@ Could_not_run_the_'vim'_program.=Could_not_run_the_'vim'_program. Could_not_save_file.=Could_not_save_file. Character_encoding_'%0'_is_not_supported.=Character_encoding_'%0'_is_not_supported. -Created_groups.=Created_groups. - crossreferenced_entries_included=crossreferenced_entries_included Current_content=Current_content @@ -1470,7 +1462,6 @@ Metadata_change=Metadata_change Changes_have_been_made_to_the_following_metadata_elements=Changes_have_been_made_to_the_following_metadata_elements Generate_groups_for_author_last_names=Generate_groups_for_author_last_names -Generate_groups_for_editor_last_names=Generate_groups_for_editor_last_names Generate_groups_from_keywords_in_a_BibTeX_field=Generate_groups_from_keywords_in_a_BibTeX_field Enforce_legal_characters_in_BibTeX_keys=Enforce_legal_characters_in_BibTeX_keys diff --git a/src/main/resources/l10n/JabRef_es.properties b/src/main/resources/l10n/JabRef_es.properties index 15bc0dd8bb1..20fc17a8989 100644 --- a/src/main/resources/l10n/JabRef_es.properties +++ b/src/main/resources/l10n/JabRef_es.properties @@ -133,10 +133,6 @@ Autolink_only_files_that_match_the_BibTeX_key=Autoenlazar_sólo_archivos_cuyo_no Automatically_create_groups=Crear_grupos_automáticamente -Automatically_create_groups_for_library.=Crear_grupos_para_base_de_datos_automáticamente - -Automatically_created_groups=Grupos_automáticamente_creados - Automatically_remove_exact_duplicates=Eliminar_automáticamente_duplicados_exactos Allow_overwriting_existing_links.=Permitir_sobreescribir_enlaces_existentes. @@ -209,8 +205,6 @@ Changed_look_and_feel_settings=Ajustes_de_aspecto_cambiados Changed_preamble=Preámbulo_cambiado -Characters_to_ignore=Caracteres_a_ignorar - Check_existing_file_links=Comprobar_archivo_enlaces_existentes Check_links=Comprobar_enlaces @@ -287,8 +281,6 @@ Could_not_run_the_'vim'_program.=No_se_puede_ejecutar_Vim Could_not_save_file.=No_se_puede_guardar_el_archivo. Character_encoding_'%0'_is_not_supported.=La_codificaciónd_de_caracteres_'%0'_no_está_soportada. -Created_groups.=Grupos_creados. - crossreferenced_entries_included=entradas_de_referencia_cruzada_incluídas Current_content=Contenido_actual @@ -1470,7 +1462,6 @@ Metadata_change=Cambio_de_metadatos Changes_have_been_made_to_the_following_metadata_elements=Se_han_efectuado_los_cambios_a_los_siguientes_elementos_de_los_metadatos Generate_groups_for_author_last_names=Generar_grupos_para_apellidos_de_autor -Generate_groups_for_editor_last_names=Generar_grupos_para_apellidos_de_editor Generate_groups_from_keywords_in_a_BibTeX_field=Generar_grupos_desde_palabras_claves_de_un_campo_BibTeX Enforce_legal_characters_in_BibTeX_keys=Uso_obligado_de_caracteres_legales_en_claves_BibTeX diff --git a/src/main/resources/l10n/JabRef_fa.properties b/src/main/resources/l10n/JabRef_fa.properties index b08808a0b2c..4bd1e8549b2 100644 --- a/src/main/resources/l10n/JabRef_fa.properties +++ b/src/main/resources/l10n/JabRef_fa.properties @@ -133,10 +133,6 @@ Autolink_only_files_that_match_the_BibTeX_key= Automatically_create_groups= -Automatically_create_groups_for_library.= - -Automatically_created_groups= - Automatically_remove_exact_duplicates= Allow_overwriting_existing_links.= @@ -209,8 +205,6 @@ Changed_look_and_feel_settings= Changed_preamble= -Characters_to_ignore= - Check_existing_file_links= Check_links= @@ -287,8 +281,6 @@ Could_not_run_the_'vim'_program.= Could_not_save_file.= Character_encoding_'%0'_is_not_supported.= -Created_groups.= - crossreferenced_entries_included= Current_content= @@ -1470,7 +1462,6 @@ Metadata_change= Changes_have_been_made_to_the_following_metadata_elements= Generate_groups_for_author_last_names= -Generate_groups_for_editor_last_names= Generate_groups_from_keywords_in_a_BibTeX_field= Enforce_legal_characters_in_BibTeX_keys= diff --git a/src/main/resources/l10n/JabRef_fr.properties b/src/main/resources/l10n/JabRef_fr.properties index 496b427a65f..6e352e031d4 100644 --- a/src/main/resources/l10n/JabRef_fr.properties +++ b/src/main/resources/l10n/JabRef_fr.properties @@ -133,10 +133,6 @@ Autolink_only_files_that_match_the_BibTeX_key=Lier_automatiquement_les_fichiers_ Automatically_create_groups=Créer_automatiquement_des_groupes -Automatically_create_groups_for_library.=Créer_automatiquement_des_groupes_pour_le_fichier. - -Automatically_created_groups=Groupes_créés_automatiquement - Automatically_remove_exact_duplicates=Supprimer_automatiquement_les_doublons_identiques Allow_overwriting_existing_links.=Ecraser_les_liens_existants. @@ -209,8 +205,6 @@ Changed_look_and_feel_settings=Changer_les_paramètres_d'apparence Changed_preamble=Préambule_modifié -Characters_to_ignore=Caractères_à_ignorer - Check_existing_file_links=Vérifier_les_liens_fichier_existants Check_links=Vérifier_les_liens @@ -287,8 +281,6 @@ Could_not_run_the_'vim'_program.=Le_programme_'vim'_n'a_pas_pu_être_lancé. Could_not_save_file.=Le_fichier_n'a_pas_pu_être_enregistré_. Character_encoding_'%0'_is_not_supported.=L'encodage_de_caractères_'%0'_n'est_pas_supporté. -Created_groups.=Groupes_créés. - crossreferenced_entries_included=Entrées_avec_références_croisées_incluses Current_content=Contenu_actuel @@ -343,15 +335,15 @@ delete_entry=effacer_l'entrée Delete_multiple_entries=Effacer_plusieurs_entrées -Delete_rows=Supprimer_des_lignes +Delete_rows=Supprimer_les_lignes Delete_strings=Supprimer_les_chaînes Deleted=Supprimé Permanently_delete_local_file=Supprimer_le_fichier_local -Delete_file= -Delete_'%0'?= +Delete_file=Supprimer_le_fichier +Delete_'%0'?=Supprimer_'%0'_? Delimit_fields_with_semicolon,_ex.=Délimiter_les_champs_par_des_points-virgules,_ex. Descending=Descendant @@ -1470,7 +1462,6 @@ Metadata_change=Changement_dans_les_métadonnées Changes_have_been_made_to_the_following_metadata_elements=Des_modifications_ont_été_faites_aux_éléments_de_métadonnées_suivants Generate_groups_for_author_last_names=Création_de_groupes_pour_les_noms_d'auteurs -Generate_groups_for_editor_last_names=Création_de_groupes_pour_les_noms_d'éditeurs Generate_groups_from_keywords_in_a_BibTeX_field=Création_de_groupes_à_partir_de_mots-clefs_d'un_champ_BibTeX Enforce_legal_characters_in_BibTeX_keys=Imposer_des_caractères_légaux_dans_les_clefs_BibTeX diff --git a/src/main/resources/l10n/JabRef_in.properties b/src/main/resources/l10n/JabRef_in.properties index 3f942296095..f5ff714aab2 100644 --- a/src/main/resources/l10n/JabRef_in.properties +++ b/src/main/resources/l10n/JabRef_in.properties @@ -133,10 +133,6 @@ Autolink_only_files_that_match_the_BibTeX_key=Tautan_otomatis_hanya_pada_berkas_ Automatically_create_groups=Otomatis_membuat_grup -Automatically_create_groups_for_library.=Otomatis_membuat_grup_untuk_basisdata. - -Automatically_created_groups=Grup_yang_dibuat_otomatis - Automatically_remove_exact_duplicates=Otomatis_menghapus_yang_sama Allow_overwriting_existing_links.=Mengijinkan_menindih_tautan_yang_ada. @@ -209,8 +205,6 @@ Changed_look_and_feel_settings=Pengaturan_penampilan_berubah Changed_preamble=Preamble_berubah -Characters_to_ignore=Karakter_diabaikan - Check_existing_file_links=Periksa_berkas_tautan_yang_sudah_ada Check_links=Periksa_tautan @@ -287,8 +281,6 @@ Could_not_run_the_'vim'_program.=Tidak_bisa_menjalankan_program_'vim'. Could_not_save_file.=Tidak_bisa_membuka_berkas. Character_encoding_'%0'_is_not_supported.=Enkoding_karakter_'%0'_tidak_didukung. -Created_groups.=Grup_dibuat. - crossreferenced_entries_included=entri_referensi_silang_diikutkan Current_content=Isi_sekarang @@ -1470,7 +1462,6 @@ Metadata_change=Perubahan_Metadata Changes_have_been_made_to_the_following_metadata_elements=Perubahan_telah_dilakukan_pada_elemen_metadata_berikut Generate_groups_for_author_last_names=Membuat_grup_untuk_nama_belakang_penulis -Generate_groups_for_editor_last_names=Membuat_grup_untuk_nama_belakang_penyunting Generate_groups_from_keywords_in_a_BibTeX_field=Membuat_grup_dari_katakunci_di_bidang_BibTeX Enforce_legal_characters_in_BibTeX_keys=Menggunakan_karakter_legal_untuk_kunci_BibTeX diff --git a/src/main/resources/l10n/JabRef_it.properties b/src/main/resources/l10n/JabRef_it.properties index bea98d15a6b..3d3e431b169 100644 --- a/src/main/resources/l10n/JabRef_it.properties +++ b/src/main/resources/l10n/JabRef_it.properties @@ -133,10 +133,6 @@ Autolink_only_files_that_match_the_BibTeX_key=Collegare_automaticamente_solo_i_f Automatically_create_groups=Crea_automaticamente_i_gruppi -Automatically_create_groups_for_library.=Crea_automaticamente_i_gruppi_per_il_library - -Automatically_created_groups=Gruppi_creati_automaticamente - Automatically_remove_exact_duplicates=Rimuovi_automaticamente_i_duplicati_esatti Allow_overwriting_existing_links.=Consenti_la_sovrascrittura_dei_collegamenti_esistenti. @@ -209,8 +205,6 @@ Changed_look_and_feel_settings=Parametri_del_"Look-and-Feel"_modificati Changed_preamble=Preambolo_modificato -Characters_to_ignore=Caratteri_da_ignorare - Check_existing_file_links=Verificare_i_collegamenti_a_file_esistenti Check_links=Verifica_i_collegamenti @@ -287,8 +281,6 @@ Could_not_run_the_'vim'_program.=Impossibile_eseguire_il_programma_'vim'. Could_not_save_file.=Impossibile_salvare_il_file. Character_encoding_'%0'_is_not_supported.=La_codifica_dei_caratteri_'%0'_non_è_supportata. -Created_groups.=Gruppi_creati - crossreferenced_entries_included=Incluse_le_voci_con_riferimenti_incrociati Current_content=Contenuto_corrente @@ -1470,7 +1462,6 @@ Metadata_change=Modifica_dei_metadati Changes_have_been_made_to_the_following_metadata_elements=Sono_stati_modificati_i_seguenti_elementi_dei_metadati Generate_groups_for_author_last_names=Genera_gruppi_in_base_al_cognome_dell'autore -Generate_groups_for_editor_last_names=Genera_gruppi_in_base_al_cognome_del_curatore Generate_groups_from_keywords_in_a_BibTeX_field=Genera_gruppi_in_base_alle_parole_chiave_in_un_campo_BibTeX Enforce_legal_characters_in_BibTeX_keys=Imponi_l'utilizzo_dei_soli_caratteri_conformi_alla_sintassi_nelle_chiavi_BibTeX diff --git a/src/main/resources/l10n/JabRef_ja.properties b/src/main/resources/l10n/JabRef_ja.properties index d4e2b913fb2..7fba66f0e8e 100644 --- a/src/main/resources/l10n/JabRef_ja.properties +++ b/src/main/resources/l10n/JabRef_ja.properties @@ -133,10 +133,6 @@ Autolink_only_files_that_match_the_BibTeX_key=BibTeX鍵に一致するファイ Automatically_create_groups=グループを自動生成 -Automatically_create_groups_for_library.=データベースのグループを自動生成 - -Automatically_created_groups=グループを自動生成しました - Automatically_remove_exact_duplicates=完全に同一な重複を自動削除 Allow_overwriting_existing_links.=既存リンクの上書きを許可する。 @@ -209,8 +205,6 @@ Changed_look_and_feel_settings=操作性設定を変更しました Changed_preamble=プリアンブルを変更しました -Characters_to_ignore=無視する文字 - Check_existing_file_links=既存のファイルリンクを確認 Check_links=リンクを確認 @@ -287,8 +281,6 @@ Could_not_run_the_'vim'_program.=「vim」プログラムを実行できませ Could_not_save_file.=ファイルを保存できませんでした Character_encoding_'%0'_is_not_supported.=。文字エンコーディング「%0」はサポートされていません。 -Created_groups.=グループを生成しました。 - crossreferenced_entries_included=相互参照している項目を取り込みました Current_content=現在の内容 @@ -1470,7 +1462,6 @@ Metadata_change=メタデータの変更 Changes_have_been_made_to_the_following_metadata_elements=以下のメタデータ要素に変更を加えました Generate_groups_for_author_last_names=著者の姓でグループを生成する -Generate_groups_for_editor_last_names=編集者の姓でグループを生成する Generate_groups_from_keywords_in_a_BibTeX_field=BibTeXフィールドのキーワードからグループを生成する Enforce_legal_characters_in_BibTeX_keys=BibTeX鍵で規則に則った文字の使用を強制する diff --git a/src/main/resources/l10n/JabRef_nl.properties b/src/main/resources/l10n/JabRef_nl.properties index 28d18f985fc..282b7b24bed 100644 --- a/src/main/resources/l10n/JabRef_nl.properties +++ b/src/main/resources/l10n/JabRef_nl.properties @@ -133,10 +133,6 @@ Autolink_only_files_that_match_the_BibTeX_key= Automatically_create_groups=Groepen_automatisch_aanmaken -Automatically_create_groups_for_library.=Automatisch_groepen_voor_library_aanmaken - -Automatically_created_groups=Automatisch_aangemaakte_groepen - Automatically_remove_exact_duplicates=Automatisch_exacte_kopie\u00ebn_verwijderen Allow_overwriting_existing_links.=Overschrijven_van_bestaande_snelkoppelingen_toestaan. @@ -209,8 +205,6 @@ Changed_look_and_feel_settings=Gewijzigde_"Look_and_Feel"-instellingen Changed_preamble=Gewijzigde_inleiding -Characters_to_ignore=Tekens_die_genegeerd_worden - Check_existing_file_links=Controleer_bestaande_bestand_snelkoppelingen Check_links=Controleer_snelkoppelingen @@ -287,8 +281,6 @@ Could_not_run_the_'vim'_program.= Could_not_save_file.=Kon_het_bestand_niet_opslaan. Character_encoding_'%0'_is_not_supported.= -Created_groups.=Groepen_aangemaakt. - crossreferenced_entries_included=inclusief_kruisgerefereerde_entries Current_content=Huidige_inhoud @@ -1470,7 +1462,6 @@ Metadata_change= Changes_have_been_made_to_the_following_metadata_elements= Generate_groups_for_author_last_names= -Generate_groups_for_editor_last_names= Generate_groups_from_keywords_in_a_BibTeX_field= Enforce_legal_characters_in_BibTeX_keys= diff --git a/src/main/resources/l10n/JabRef_no.properties b/src/main/resources/l10n/JabRef_no.properties index 6829796cfa4..8f9f291140a 100644 --- a/src/main/resources/l10n/JabRef_no.properties +++ b/src/main/resources/l10n/JabRef_no.properties @@ -133,10 +133,6 @@ Autolink_only_files_that_match_the_BibTeX_key=Autolink_bare_filer_med_navn_som_s Automatically_create_groups=Generer_grupper_automatisk -Automatically_create_groups_for_library.=Generer_grupper_for_libraryn. - -Automatically_created_groups=Genererte_grupper_automatisk - Automatically_remove_exact_duplicates=Fjern_eksakte_duplikater_automatisk Allow_overwriting_existing_links.=Tillat_overskriving_av_eksisterende_linker. @@ -209,8 +205,6 @@ Changed_look_and_feel_settings=Endret_oppsett_av_grensesnitt Changed_preamble=Endret_preamble -Characters_to_ignore=Ignorer_f\u00f8lgende_tegn - Check_existing_file_links=Sjekk_eksisterende_fil-linker Check_links=Sjekk_eksterne_linker @@ -287,8 +281,6 @@ Could_not_run_the_'vim'_program.=Kunne_ikke_kj\u00f8re_'vim'-programmet Could_not_save_file.= Character_encoding_'%0'_is_not_supported.=Tegnkodingen_'%0'_er_ikke_st\u00f8ttet. -Created_groups.=Opprettet_grupper. - crossreferenced_entries_included=refererte_enheter_inkludert Current_content=N\u00e5v\u00e6rende_innhold @@ -1470,7 +1462,6 @@ Metadata_change=Endring_av_metadata Changes_have_been_made_to_the_following_metadata_elements=Endringer_er_gjort_for_de_f\u00b8lgende_metadata-elementene Generate_groups_for_author_last_names=Generer_grupper_for_etternavn_fra_author-feltet -Generate_groups_for_editor_last_names=Generer_grupper_for_etternavn_fra_editor-feltet Generate_groups_from_keywords_in_a_BibTeX_field=Generer_grupper_fra_n\u00b8kkelord_i_et_BibTeX-felt Enforce_legal_characters_in_BibTeX_keys=Forby_tegn_i_BibTeX-n\u00b8kler_som_ikke_aksepteres_av_BibTeX diff --git a/src/main/resources/l10n/JabRef_pt_BR.properties b/src/main/resources/l10n/JabRef_pt_BR.properties index 57f3bdb90e8..4b1fa023303 100644 --- a/src/main/resources/l10n/JabRef_pt_BR.properties +++ b/src/main/resources/l10n/JabRef_pt_BR.properties @@ -133,10 +133,6 @@ Autolink_only_files_that_match_the_BibTeX_key=Criar_links_automaticamente_soment Automatically_create_groups=Criar_grupos_automaticamente -Automatically_create_groups_for_library.=Criar_grupos_automaticamente_para_a_base_de_dados. - -Automatically_created_groups=Grupos_criados_automaticamente - Automatically_remove_exact_duplicates=Remover_automaticamente_duplicatas_exatas Allow_overwriting_existing_links.=Sobrescrever_links_existentes. @@ -209,8 +205,6 @@ Changed_look_and_feel_settings=Configurações_do_esquema_de_cores_modificadas Changed_preamble=Preâmbulo_modificado -Characters_to_ignore=Caracteres_para_ignorar - Check_existing_file_links=Verificar_links_de_arquivos_existentes Check_links=Verificar_links @@ -287,8 +281,6 @@ Could_not_run_the_'vim'_program.=Não_foi_possível_executar_o_programa_'vim'. Could_not_save_file.=Não_foi_possível_salvar_o_arquivo. Character_encoding_'%0'_is_not_supported.=A_codificação_de_caracteres_'%0'_não_é_suportada. -Created_groups.=Grupos_criados. - crossreferenced_entries_included=Registros_com_referências_cruzadas_incluídos Current_content=Conteúdo_atual @@ -1470,7 +1462,6 @@ Metadata_change=Mudança_de_metadados Changes_have_been_made_to_the_following_metadata_elements=Mudanças_foram_realizadas_nos_seguintes_elementos_de_metadados Generate_groups_for_author_last_names=Gerar_grupos_a_partir_dos_últimos_nomes_dos_autores -Generate_groups_for_editor_last_names=Gerar_grupos_pelos_últimos_nomes_dos_editores Generate_groups_from_keywords_in_a_BibTeX_field=Gerar_grupos_a_partir_de_palavras_chaves_em_um_campo_BibTeX Enforce_legal_characters_in_BibTeX_keys=Forçar_caracteres_permitidos_em_chaves_BibTeX diff --git a/src/main/resources/l10n/JabRef_ru.properties b/src/main/resources/l10n/JabRef_ru.properties index 4821b1b4342..70343569f7a 100644 --- a/src/main/resources/l10n/JabRef_ru.properties +++ b/src/main/resources/l10n/JabRef_ru.properties @@ -133,10 +133,6 @@ Autolink_only_files_that_match_the_BibTeX_key=Автоматическая_пр Automatically_create_groups=Автоматическое_создание_групп -Automatically_create_groups_for_library.=Автоматическое_создание_групп_для_БД. - -Automatically_created_groups=Автоматически_созданные_группы - Automatically_remove_exact_duplicates=Автоматически_удалять_полные_дубликаты Allow_overwriting_existing_links.=Разрешить_перезапись_текущих_ссылок. @@ -209,8 +205,6 @@ Changed_look_and_feel_settings=Измененные_настройки_инте Changed_preamble=Измененная_преамбула -Characters_to_ignore=Не_учитывать_знаки - Check_existing_file_links=Проверить_текущие_ссылки_файл Check_links=Проверить_ссылки @@ -287,8 +281,6 @@ Could_not_run_the_'vim'_program.=Не_удалось_запустить_прил Could_not_save_file.=Не_удалось_сохранить_файл. Character_encoding_'%0'_is_not_supported.=Кодировка_'%0'_не_поддерживается. -Created_groups.=Созданные_группы. - crossreferenced_entries_included=записи_с_перекрестными_ссылками Current_content=Текущее_содержимое @@ -1470,7 +1462,6 @@ Metadata_change=Изменение_метаданных Changes_have_been_made_to_the_following_metadata_elements=Произведены_изменения_для_следующих_элементов_метаданных Generate_groups_for_author_last_names=Создание_групп_для_фамилий_авторов -Generate_groups_for_editor_last_names=Создание_групп_для_фамилий_редакторов Generate_groups_from_keywords_in_a_BibTeX_field=Создание_групп_из_ключевых_слов_в_поле_BibTeX Enforce_legal_characters_in_BibTeX_keys=Принудительное_использование_допустимого_набора_символов_в_ключах_BibTeX diff --git a/src/main/resources/l10n/JabRef_sv.properties b/src/main/resources/l10n/JabRef_sv.properties index 0b32d130b65..998a93f4302 100644 --- a/src/main/resources/l10n/JabRef_sv.properties +++ b/src/main/resources/l10n/JabRef_sv.properties @@ -133,10 +133,6 @@ Autolink_only_files_that_match_the_BibTeX_key=Länka_bara_filer_vars_namn_är_Bi Automatically_create_groups=Skapa_grupper_automatiskt -Automatically_create_groups_for_library.=Skapa_grupper_automatiskt_för_libraryn. - -Automatically_created_groups=Skapade_grupper_automatiskt - Automatically_remove_exact_duplicates=Ta_bort_exakta_dubbletter_automatiskt Allow_overwriting_existing_links.=Tillåt_att_befintliga_länkar_skrivs_över. @@ -209,8 +205,6 @@ Changed_look_and_feel_settings=Ändra_inställningar_för_look-and-feel Changed_preamble=Ändrade_preamble -Characters_to_ignore=Bokstäver_att_ignorera - Check_existing_file_links=Kontrollera_befintliga_fillänkar Check_links=Kontrollera_länkar @@ -287,8 +281,6 @@ Could_not_run_the_'vim'_program.=Kunde_inte_köra_'vim'. Could_not_save_file.=Kunde_inte_spara_fil. Character_encoding_'%0'_is_not_supported.=Teckenkodningen_'%0'_stöds_inte. -Created_groups.=Skapade_grupper. - crossreferenced_entries_included=korsrefererade_poster_inkluderade Current_content=Nuvarande_innehåll @@ -1470,7 +1462,6 @@ Metadata_change=Ändring_i_metadata Changes_have_been_made_to_the_following_metadata_elements=Ändringar_har_gjorts_i_följande_metadata-element Generate_groups_for_author_last_names=Generera_grupper_baserat_på_författarefternamn -Generate_groups_for_editor_last_names=Generera_grupper_baserat_på_editorefternamn Generate_groups_from_keywords_in_a_BibTeX_field=Generera_grupper_baserat_på_nyckelord_i_ett_fält Enforce_legal_characters_in_BibTeX_keys=Framtvinga_giltiga_tecken_i_BibTeX-nycklar diff --git a/src/main/resources/l10n/JabRef_tr.properties b/src/main/resources/l10n/JabRef_tr.properties index 13e091f4374..61770323740 100644 --- a/src/main/resources/l10n/JabRef_tr.properties +++ b/src/main/resources/l10n/JabRef_tr.properties @@ -133,10 +133,6 @@ Autolink_only_files_that_match_the_BibTeX_key=Yalnızca_BibTeX_anahtarıyla_eşl Automatically_create_groups=Grupları_otomatik_oluştur -Automatically_create_groups_for_library.=Veritabanı_için_grupları_otomatik_oluştur - -Automatically_created_groups=Otomatik_oluşturulmuş_gruplar - Automatically_remove_exact_duplicates=Tıpkı_çift_nüshaları_otomatik_sil Allow_overwriting_existing_links.=Mevcut_linklerin_üzerine_yazmaya_izin_ver. @@ -209,8 +205,6 @@ Changed_look_and_feel_settings=Görünüm_ve_tema_ayarları_değişti Changed_preamble=Öncül_değişti -Characters_to_ignore=Yoksayılacak_karakterler - Check_existing_file_links=Mevcut_dosya_linki_kontrol_ediniz Check_links=Linkleri_kontrol_ediniz @@ -287,8 +281,6 @@ Could_not_run_the_'vim'_program.='Vim'_programı_çalıştırılamıyor. Could_not_save_file.=Dosya_kaydedilemiyor. Character_encoding_'%0'_is_not_supported.='%0'_karakter_kodlaması_desteklenmiyor. -Created_groups.=Oluşturulmuş_gruplar. - crossreferenced_entries_included=çapraz_bağlantılı_girdiler_dahil_edildi Current_content=Güncel_içerik @@ -1470,7 +1462,6 @@ Metadata_change=Metadata_değişikliği Changes_have_been_made_to_the_following_metadata_elements=Aşağıdaki_metadata_ögelerinde_değişiklik_yapıldı Generate_groups_for_author_last_names=Yazar_soyadları_için_grup_oluştur -Generate_groups_for_editor_last_names=Editör_soyadları_için_grup_oluştur Generate_groups_from_keywords_in_a_BibTeX_field=Bir_BibTeX_alanındaki_anahtar_sözcüklerden_grup_oluştur Enforce_legal_characters_in_BibTeX_keys=BibTeX_anahtarlarında_yasal_karakterleri_zorla diff --git a/src/main/resources/l10n/JabRef_vi.properties b/src/main/resources/l10n/JabRef_vi.properties index 9203cb74275..6f9601a172c 100644 --- a/src/main/resources/l10n/JabRef_vi.properties +++ b/src/main/resources/l10n/JabRef_vi.properties @@ -133,10 +133,6 @@ Autolink_only_files_that_match_the_BibTeX_key=Chỉ_tự_động_liên_kết_cá Automatically_create_groups=Tự_động_tạo_các_nhóm -Automatically_create_groups_for_library.=Tự_động_tạo_các_nhóm_dùng_cho_CSDL. - -Automatically_created_groups=Các_nhóm_được_tạo_ra_tự_động - Automatically_remove_exact_duplicates=Tự_động_loại_bỏ_các_mục_trùng_nhau Allow_overwriting_existing_links.=Cho_phép_ghi_đè_các_liên_kết_hiện_có. @@ -209,8 +205,6 @@ Changed_look_and_feel_settings=Các_thiết_lập_diện_mạo_được_thay_đ Changed_preamble=Phần_mở_đầu_được_thay_đổi -Characters_to_ignore=Các_ký_tự_bỏ_qua - Check_existing_file_links=Kiểm_tra_tập_tin_liên_kết_hiện_có Check_links=Kiểm_tra_các_liên_kết @@ -287,8 +281,6 @@ Could_not_run_the_'vim'_program.=Không_thể_chạy_chương_trình_'vim'. Could_not_save_file.=Không_thể_lưu_tập_tin. Character_encoding_'%0'_is_not_supported.=Mã_hóa_ký_tự_'%0'_không_được_hỗ_trợ. -Created_groups.=Các_nhóm_được_tạo_ra. - crossreferenced_entries_included=các_mục_có_tham_chiếu_chéo_được_đưa_vào Current_content=Nội_dung_hiện_tại @@ -1470,7 +1462,6 @@ Metadata_change=Thay_đổi_đặc_tả_dữ_liệu Changes_have_been_made_to_the_following_metadata_elements=Các_thay_đổi_đã_được_thực_hiện_cho_những_thành_phần_đặc_tả_CSDL_sau Generate_groups_for_author_last_names=Tạo_các_nhóm_cho_họ_của_tác_giả -Generate_groups_for_editor_last_names=Tạo_các_nhóm_cho_tên_họ_của_người_biên_tập Generate_groups_from_keywords_in_a_BibTeX_field=Tạo_các_nhóm_theo_từ_khóa_trong_một_dữ_liệu_BibTeX Enforce_legal_characters_in_BibTeX_keys=Buộc_phải_dùng_những_ký_tự_hợp_lệ_trong_khóa_BibTeX diff --git a/src/main/resources/l10n/JabRef_zh.properties b/src/main/resources/l10n/JabRef_zh.properties index 99b8db5b7b9..44a84d5e503 100644 --- a/src/main/resources/l10n/JabRef_zh.properties +++ b/src/main/resources/l10n/JabRef_zh.properties @@ -133,10 +133,6 @@ Autolink_only_files_that_match_the_BibTeX_key=自动链接文件名匹配_BibTeX Automatically_create_groups=自动创建分组 -Automatically_create_groups_for_library.=自动为数据库创建分组。 - -Automatically_created_groups=自动创建的分组 - Automatically_remove_exact_duplicates=自动移除完全重复的项 Allow_overwriting_existing_links.=允许覆盖已有的链接。 @@ -209,8 +205,6 @@ Changed_look_and_feel_settings=已修改显示效果_(look_and_feel)_设置 Changed_preamble=已修改导言区_(preamble) -Characters_to_ignore=要忽略的字符 - Check_existing_file_links=检查存在的文件链接 Check_links=核对链接 @@ -287,8 +281,6 @@ Could_not_run_the_'vim'_program.=无法运行_'vim'_程序。 Could_not_save_file.=无法保存文件 Character_encoding_'%0'_is_not_supported.=,不支持编码_'%0'。 -Created_groups.=建立分组 - crossreferenced_entries_included=包含交叉引用的记录 Current_content=当前内容 @@ -1470,7 +1462,6 @@ Metadata_change=元数据改变 Changes_have_been_made_to_the_following_metadata_elements=下列元数据元素被改变 Generate_groups_for_author_last_names=用作者的姓_(last_name)_创建分组 -Generate_groups_for_editor_last_names=用编者的姓_(last_name)_创建分组 Generate_groups_from_keywords_in_a_BibTeX_field=用_BibTeX_域中的关键词创建分组 Enforce_legal_characters_in_BibTeX_keys=强制在_BibTeX_键值中使用合法字符 diff --git a/src/test/java/org/jabref/ArchitectureTests.java b/src/test/java/org/jabref/ArchitectureTests.java index ac7caa38b17..d3b8b1decac 100644 --- a/src/test/java/org/jabref/ArchitectureTests.java +++ b/src/test/java/org/jabref/ArchitectureTests.java @@ -29,21 +29,19 @@ public class ArchitectureTests { private static final String CLASS_ORG_JABREF_GLOBALS = "org.jabref.Globals"; private static final String EXCEPTION_PACKAGE_JAVA_AWT_GEOM = "java.awt.geom"; - - private Map> exceptionStrings; - private final String firstPackage; private final String secondPackage; + private Map> exceptions; public ArchitectureTests(String firstPackage, String secondPackage) { this.firstPackage = firstPackage; this.secondPackage = secondPackage; - //add exceptions for the architectural test here - //Note that bending the architectural constraints should not be done inconsiderately - exceptionStrings = new HashMap<>(); - exceptionStrings.put(PACKAGE_ORG_JABREF_LOGIC, - Arrays.asList(EXCEPTION_PACKAGE_JAVA_AWT_GEOM)); + // Add exceptions for the architectural test here + // Note that bending the architectural constraints should not be done inconsiderately + exceptions = new HashMap<>(); + exceptions.put(PACKAGE_ORG_JABREF_LOGIC, + Collections.singletonList(EXCEPTION_PACKAGE_JAVA_AWT_GEOM)); } @@ -67,9 +65,10 @@ public static Iterable data() { @Test public void firstPackageIsIndependentOfSecondPackage() throws IOException { - Predicate isExceptionPackage = (s) -> s.startsWith("import " + secondPackage) && !(exceptionStrings.get(firstPackage).stream() - .filter(exception -> s.startsWith("import " + exception)).findAny().isPresent() - ); + Predicate isExceptionPackage = (s) -> + s.startsWith("import " + secondPackage) + && exceptions.getOrDefault(firstPackage, Collections.emptyList()).stream() + .noneMatch(exception -> s.startsWith("import " + exception)); Predicate isPackage = (s) -> s.startsWith("package " + firstPackage); @@ -77,22 +76,21 @@ public void firstPackageIsIndependentOfSecondPackage() throws IOException { .filter(p -> p.toString().endsWith(".java")) .filter(p -> { try { - return Files.readAllLines(p, StandardCharsets.UTF_8).stream() - .filter(isPackage).findAny().isPresent(); + return Files.readAllLines(p, StandardCharsets.UTF_8).stream().anyMatch(isPackage); } catch (IOException e) { return false; } }) .filter(p -> { try { - return Files.readAllLines(p, StandardCharsets.UTF_8).stream() - .filter(isExceptionPackage).findAny().isPresent(); + return Files.readAllLines(p, StandardCharsets.UTF_8).stream().anyMatch(isExceptionPackage); } catch (IOException e) { return false; } }).collect(Collectors.toList()); - Assert.assertEquals(Collections.emptyList(), files); + Assert.assertEquals("The following classes are not allowed to depend on " + secondPackage, + Collections.emptyList(), files); } } diff --git a/src/test/java/org/jabref/gui/groups/GroupNodeViewModelTest.java b/src/test/java/org/jabref/gui/groups/GroupNodeViewModelTest.java new file mode 100644 index 00000000000..2b71b6bc3f1 --- /dev/null +++ b/src/test/java/org/jabref/gui/groups/GroupNodeViewModelTest.java @@ -0,0 +1,56 @@ +package org.jabref.gui.groups; + +import javafx.collections.FXCollections; + +import org.jabref.gui.StateManager; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.groups.AbstractGroup; +import org.jabref.model.groups.GroupHierarchyType; +import org.jabref.model.groups.WordKeywordGroup; + +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class GroupNodeViewModelTest { + + private StateManager stateManager; + private BibDatabaseContext databaseContext; + private GroupNodeViewModel viewModel; + + @Before + public void setUp() throws Exception { + stateManager = mock(StateManager.class); + when(stateManager.getSelectedEntries()).thenReturn(FXCollections.emptyObservableList()); + databaseContext = new BibDatabaseContext(); + + viewModel = getViewModelForGroup( + new WordKeywordGroup("Test group", GroupHierarchyType.INDEPENDENT, "test", "search", true, ',', false)); + + } + + @Test + public void getDisplayNameConvertsLatexToUnicode() throws Exception { + GroupNodeViewModel viewModel = getViewModelForGroup( + new WordKeywordGroup("\\beta", GroupHierarchyType.INDEPENDENT, "test", "search", true, ',', false)); + assertEquals("β", viewModel.getDisplayName()); + } + + @Test + public void alwaysMatchedByEmptySearchString() throws Exception { + assertTrue(viewModel.isMatchedBy("")); + } + + @Test + public void isMatchedIfContainsPartOfSearchString() throws Exception { + assertTrue(viewModel.isMatchedBy("est")); + } + + private GroupNodeViewModel getViewModelForGroup(AbstractGroup group) { + return new GroupNodeViewModel(databaseContext, stateManager, group); + } +} diff --git a/src/test/java/org/jabref/gui/groups/GroupsUtilTest.java b/src/test/java/org/jabref/gui/groups/GroupsUtilTest.java deleted file mode 100644 index 9d608506498..00000000000 --- a/src/test/java/org/jabref/gui/groups/GroupsUtilTest.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.jabref.gui.groups; - -import java.io.BufferedReader; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -import org.jabref.logic.importer.ParserResult; -import org.jabref.logic.importer.fileformat.BibtexParser; -import org.jabref.model.database.BibDatabase; -import org.jabref.preferences.JabRefPreferences; - -import org.junit.Assert; -import org.junit.Test; - -public class GroupsUtilTest { - - - @Test - public void test() throws IOException { - try (BufferedReader fr = Files.newBufferedReader(Paths.get("src/test/resources/testbib/testjabref.bib"), - StandardCharsets.UTF_8)) { - - ParserResult result = new BibtexParser(JabRefPreferences.getInstance().getImportFormatPreferences()).parse(fr); - - BibDatabase db = result.getDatabase(); - - List fieldList = new ArrayList<>(); - fieldList.add("author"); - - Set authorSet = AutoGroupDialog.findAuthorLastNames(db, fieldList); - Assert.assertTrue(authorSet.contains("Brewer")); - Assert.assertEquals(15, authorSet.size()); - - Set keywordSet = AutoGroupDialog.findDeliminatedWordsInField(db, "keywords", ";"); - Assert.assertTrue(keywordSet.contains("Brain")); - Assert.assertEquals(60, keywordSet.size()); - - Set wordSet = AutoGroupDialog.findAllWordsInField(db, "month", ""); - Assert.assertTrue(wordSet.contains("Feb")); - Assert.assertTrue(wordSet.contains("Mar")); - Assert.assertTrue(wordSet.contains("May")); - Assert.assertTrue(wordSet.contains("Jul")); - Assert.assertTrue(wordSet.contains("Dec")); - Assert.assertEquals(5, wordSet.size()); - } - } - -} diff --git a/src/test/java/org/jabref/gui/util/RecursiveTreeItemTest.java b/src/test/java/org/jabref/gui/util/RecursiveTreeItemTest.java new file mode 100644 index 00000000000..6f06b4012db --- /dev/null +++ b/src/test/java/org/jabref/gui/util/RecursiveTreeItemTest.java @@ -0,0 +1,58 @@ +package org.jabref.gui.util; + +import java.util.Collections; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.scene.control.TreeItem; + +import org.jabref.model.TreeNode; +import org.jabref.model.TreeNodeTestData; + +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class RecursiveTreeItemTest { + + private RecursiveTreeItem rootTreeItem; + private TreeNodeTestData.TreeNodeMock root; + private ObjectProperty> filterPredicate; + private TreeNodeTestData.TreeNodeMock node; + + @Before + public void setUp() throws Exception { + root = new TreeNodeTestData.TreeNodeMock(); + node = TreeNodeTestData.getNodeInSimpleTree(root); + node.setName("test node"); + + filterPredicate = new SimpleObjectProperty<>(); + + rootTreeItem = new RecursiveTreeItem<>(root, TreeNode::getChildren, filterPredicate); + } + + @Test + public void addsAllChildrenNodes() throws Exception { + assertEquals(root.getChildren(), rootTreeItem.getChildren().stream().map(TreeItem::getValue).collect(Collectors.toList())); + } + + @Test + public void addsAllChildrenOfChildNode() throws Exception { + assertEquals( + root.getChildAt(1).get().getChildren(), + rootTreeItem.getChildren().get(1).getChildren().stream().map(TreeItem::getValue).collect(Collectors.toList())); + } + + @Test + public void respectsFilter() throws Exception { + filterPredicate.setValue(item -> item.getName().contains("test")); + + assertEquals(Collections.singletonList(node.getParent().get()), rootTreeItem.getChildren().stream().map(TreeItem::getValue).collect(Collectors.toList())); + assertEquals( + Collections.singletonList(node), + rootTreeItem.getChildren().get(0).getChildren().stream().map(TreeItem::getValue).collect(Collectors.toList())); + } +} diff --git a/src/test/java/org/jabref/logic/bibtex/LatexFieldFormatterTests.java b/src/test/java/org/jabref/logic/bibtex/LatexFieldFormatterTests.java index a8422abfd34..918296d0998 100644 --- a/src/test/java/org/jabref/logic/bibtex/LatexFieldFormatterTests.java +++ b/src/test/java/org/jabref/logic/bibtex/LatexFieldFormatterTests.java @@ -81,4 +81,32 @@ public void removeWhitespaceFromNonMultiLineFields() throws Exception { assertEquals(expected, title); assertEquals(expected, any); } + + @Test(expected = IllegalArgumentException.class) + public void reportUnbalancedBracing() { + String unbalanced = "{"; + + formatter.format(unbalanced, "anyfield"); + } + + @Test(expected = IllegalArgumentException.class) + public void reportUnbalancedBracingWithEscapedBraces() { + String unbalanced = "{\\}"; + + formatter.format(unbalanced, "anyfield"); + } + + @Test + public void tolerateBalancedBrace() { + String text = "Incorporating evolutionary {Measures into Conservation Prioritization}"; + + assertEquals("{" + text + "}", formatter.format(text, "anyfield")); + } + + @Test + public void tolerateEscapeCharacters() { + String text = "Incorporating {\\O}evolutionary {Measures into Conservation Prioritization}"; + + assertEquals("{" + text + "}", formatter.format(text, "anyfield")); + } } diff --git a/src/test/java/org/jabref/logic/importer/MimeTypeDetectorTest.java b/src/test/java/org/jabref/logic/net/MimeTypeDetectorTest.java similarity index 71% rename from src/test/java/org/jabref/logic/importer/MimeTypeDetectorTest.java rename to src/test/java/org/jabref/logic/net/MimeTypeDetectorTest.java index fc1b7ee01be..ce6efd9f64b 100644 --- a/src/test/java/org/jabref/logic/importer/MimeTypeDetectorTest.java +++ b/src/test/java/org/jabref/logic/net/MimeTypeDetectorTest.java @@ -1,5 +1,6 @@ -package org.jabref.logic.importer; +package org.jabref.logic.net; +import java.io.IOException; import java.net.URISyntaxException; import com.github.tomakehurst.wiremock.junit.WireMockRule; @@ -20,7 +21,7 @@ public class MimeTypeDetectorTest { public WireMockRule wireMockRule = new WireMockRule(); @Test - public void handlePermanentRedirections() { + public void handlePermanentRedirections() throws IOException { String redirectedUrl = "http://localhost:8080/redirection"; stubFor(any(urlEqualTo("/redirection")) @@ -31,35 +32,29 @@ public void handlePermanentRedirections() { ) ); - assertTrue(MimeTypeDetector.isPdfContentType(redirectedUrl)); + assertTrue(new URLDownload(redirectedUrl).isMimeType("application/pdf")); } @Test - public void beFalseForInvalidUrl() { - String invalidUrl = "thisisnourl"; - assertFalse(MimeTypeDetector.isPdfContentType(invalidUrl)); - } - - @Test - public void beFalseForUnreachableUrl() { + public void beFalseForUnreachableUrl() throws IOException { String invalidUrl = "http://idontknowthisurlforsure.de"; - assertFalse(MimeTypeDetector.isPdfContentType(invalidUrl)); + assertFalse(new URLDownload(invalidUrl).isMimeType("application/pdf")); } @Test - public void beTrueForPdfMimeType() { + public void beTrueForPdfMimeType() throws IOException { String pdfUrl = "http://docs.oasis-open.org/wsbpel/2.0/OS/wsbpel-v2.0-OS.pdf"; - assertTrue(MimeTypeDetector.isPdfContentType(pdfUrl)); + assertTrue(new URLDownload(pdfUrl).isMimeType("application/pdf")); } @Test - public void beTrueForLocalPdfUri() throws URISyntaxException { + public void beTrueForLocalPdfUri() throws URISyntaxException, IOException { String localPath = MimeTypeDetectorTest.class.getResource("empty.pdf").toURI().toASCIIString(); - assertTrue(MimeTypeDetector.isPdfContentType(localPath)); + assertTrue(new URLDownload(localPath).isMimeType("application/pdf")); } @Test - public void beTrueForPDFMimeTypeVariations() { + public void beTrueForPDFMimeTypeVariations() throws IOException { String mimeTypeVariation = "http://localhost:8080/mimevariation"; stubFor(any(urlEqualTo("/mimevariation")) @@ -68,11 +63,11 @@ public void beTrueForPDFMimeTypeVariations() { ) ); - assertTrue(MimeTypeDetector.isPdfContentType(mimeTypeVariation)); + assertTrue(new URLDownload(mimeTypeVariation).isMimeType("application/pdf")); } @Test - public void beAbleToUseHeadRequest() { + public void beAbleToUseHeadRequest() throws IOException { String mimeTypeVariation = "http://localhost:8080/mimevariation"; stubFor(head(urlEqualTo("/mimevariation")) @@ -81,11 +76,11 @@ public void beAbleToUseHeadRequest() { ) ); - assertTrue(MimeTypeDetector.isPdfContentType(mimeTypeVariation)); + assertTrue(new URLDownload(mimeTypeVariation).isMimeType("application/pdf")); } @Test - public void beAbleToUseGetRequest() { + public void beAbleToUseGetRequest() throws IOException { String mimeTypeVariation = "http://localhost:8080/mimevariation"; stubFor(head(urlEqualTo("/mimevariation")) @@ -99,6 +94,6 @@ public void beAbleToUseGetRequest() { ) ); - assertTrue(MimeTypeDetector.isPdfContentType(mimeTypeVariation)); + assertTrue(new URLDownload(mimeTypeVariation).isMimeType("application/pdf")); } } diff --git a/src/test/java/org/jabref/logic/net/URLDownloadTest.java b/src/test/java/org/jabref/logic/net/URLDownloadTest.java index d46fe25aafb..8af5396a089 100644 --- a/src/test/java/org/jabref/logic/net/URLDownloadTest.java +++ b/src/test/java/org/jabref/logic/net/URLDownloadTest.java @@ -3,7 +3,6 @@ import java.io.File; import java.io.IOException; import java.net.URL; -import java.nio.charset.StandardCharsets; import java.nio.file.Path; import org.jabref.preferences.JabRefPreferences; @@ -17,8 +16,7 @@ public class URLDownloadTest { public void testStringDownloadWithSetEncoding() throws IOException { URLDownload dl = new URLDownload(new URL("http://www.google.com")); - Assert.assertTrue("google.com should contain google", - dl.downloadToString(StandardCharsets.UTF_8).contains("Google")); + Assert.assertTrue("google.com should contain google", dl.asString().contains("Google")); } @Test @@ -26,7 +24,7 @@ public void testStringDownload() throws IOException { URLDownload dl = new URLDownload(new URL("http://www.google.com")); Assert.assertTrue("google.com should contain google", - dl.downloadToString(JabRefPreferences.getInstance().getDefaultEncoding()).contains("Google")); + dl.asString(JabRefPreferences.getInstance().getDefaultEncoding()).contains("Google")); } @Test @@ -34,7 +32,7 @@ public void testFileDownload() throws IOException { File destination = File.createTempFile("jabref-test", ".html"); try { URLDownload dl = new URLDownload(new URL("http://www.google.com")); - dl.downloadToFile(destination); + dl.toFile(destination.toPath()); Assert.assertTrue("file must exist", destination.exists()); } finally { // cleanup @@ -48,14 +46,14 @@ public void testFileDownload() throws IOException { public void testDetermineMimeType() throws IOException { URLDownload dl = new URLDownload(new URL("http://www.google.com")); - Assert.assertTrue(dl.determineMimeType().startsWith("text/html")); + Assert.assertTrue(dl.getMimeType().startsWith("text/html")); } @Test public void downloadToTemporaryFilePathWithoutFileSavesAsTmpFile() throws IOException { URLDownload google = new URLDownload(new URL("http://www.google.com")); - String path = google.downloadToTemporaryFile().toString(); + String path = google.toTemporaryFile().toString(); Assert.assertTrue(path, path.endsWith(".tmp")); } @@ -63,7 +61,7 @@ public void downloadToTemporaryFilePathWithoutFileSavesAsTmpFile() throws IOExce public void downloadToTemporaryFileKeepsName() throws IOException { URLDownload google = new URLDownload(new URL("https://github.com/JabRef/jabref/blob/master/LICENSE.md")); - String path = google.downloadToTemporaryFile().toString(); + String path = google.toTemporaryFile().toString(); Assert.assertTrue(path, path.contains("LICENSE") && path.endsWith(".md")); } @@ -71,7 +69,7 @@ public void downloadToTemporaryFileKeepsName() throws IOException { public void downloadOfFTPSucceeds() throws IOException { URLDownload ftp = new URLDownload(new URL("ftp://ftp.informatik.uni-stuttgart.de/pub/library/ncstrl.ustuttgart_fi/INPROC-2016-15/INPROC-2016-15.pdf")); - Path path = ftp.downloadToTemporaryFile(); + Path path = ftp.toTemporaryFile(); Assert.assertNotNull(path); } @@ -79,7 +77,7 @@ public void downloadOfFTPSucceeds() throws IOException { public void downloadOfHttpSucceeds() throws IOException { URLDownload ftp = new URLDownload(new URL("http://www.jabref.org")); - Path path = ftp.downloadToTemporaryFile(); + Path path = ftp.toTemporaryFile(); Assert.assertNotNull(path); } @@ -87,7 +85,7 @@ public void downloadOfHttpSucceeds() throws IOException { public void downloadOfHttpsSucceeds() throws IOException { URLDownload ftp = new URLDownload(new URL("https://www.jabref.org")); - Path path = ftp.downloadToTemporaryFile(); + Path path = ftp.toTemporaryFile(); Assert.assertNotNull(path); } diff --git a/src/test/java/org/jabref/model/TreeNodeTest.java b/src/test/java/org/jabref/model/TreeNodeTest.java index fb7a336a18f..841dfaeab29 100644 --- a/src/test/java/org/jabref/model/TreeNodeTest.java +++ b/src/test/java/org/jabref/model/TreeNodeTest.java @@ -21,88 +21,7 @@ public class TreeNodeTest { @Mock - Consumer subscriber; - - /** - * Gets the marked node in the following tree: - * Root - * A - * A (= parent) - * B (<-- this) - */ - private TreeNodeMock getNodeInSimpleTree(TreeNodeMock root) { - root.addChild(new TreeNodeMock()); - TreeNodeMock parent = new TreeNodeMock(); - root.addChild(parent); - TreeNodeMock node = new TreeNodeMock(); - parent.addChild(node); - return node; - } - - private TreeNodeMock getNodeInSimpleTree() { - return getNodeInSimpleTree(new TreeNodeMock()); - } - - /** - * Gets the marked node in the following tree: - * Root - * A - * A - * A (= grand parent) - * B - * B (= parent) - * C (<-- this) - * D (= child) - * C - * C - * C - * B - * B - * A - */ - private TreeNodeMock getNodeInComplexTree(TreeNodeMock root) { - root.addChild(new TreeNodeMock()); - root.addChild(new TreeNodeMock()); - TreeNodeMock grandParent = new TreeNodeMock(); - root.addChild(grandParent); - root.addChild(new TreeNodeMock()); - - grandParent.addChild(new TreeNodeMock()); - TreeNodeMock parent = new TreeNodeMock(); - grandParent.addChild(parent); - grandParent.addChild(new TreeNodeMock()); - grandParent.addChild(new TreeNodeMock()); - - TreeNodeMock node = new TreeNodeMock(); - parent.addChild(node); - parent.addChild(new TreeNodeMock()); - parent.addChild(new TreeNodeMock()); - parent.addChild(new TreeNodeMock()); - - node.addChild(new TreeNodeMock()); - return node; - } - - private TreeNodeMock getNodeInComplexTree() { - return getNodeInComplexTree(new TreeNodeMock()); - } - - /** - * Gets the marked in the following tree: - * Root - * A - * A - * A (<- this) - * A - */ - private TreeNodeMock getNodeAsChild(TreeNodeMock root) { - root.addChild(new TreeNodeMock()); - root.addChild(new TreeNodeMock()); - TreeNodeMock node = new TreeNodeMock(); - root.addChild(node); - root.addChild(new TreeNodeMock()); - return node; - } + Consumer subscriber; @Test(expected = UnsupportedOperationException.class) public void constructorChecksThatClassImplementsCorrectInterface() { @@ -111,13 +30,13 @@ public void constructorChecksThatClassImplementsCorrectInterface() { @Test public void constructorExceptsCorrectImplementation() { - TreeNodeMock treeNode = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock treeNode = new TreeNodeTestData.TreeNodeMock(); assertNotNull(treeNode); } @Test public void newTreeNodeHasNoParentOrChildren() { - TreeNodeMock treeNode = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock treeNode = new TreeNodeTestData.TreeNodeMock(); assertEquals(Optional.empty(), treeNode.getParent()); assertEquals(Collections.emptyList(), treeNode.getChildren()); assertNotNull(treeNode); @@ -125,126 +44,126 @@ public void newTreeNodeHasNoParentOrChildren() { @Test public void getIndexedPathFromRootReturnsEmptyListForRoot() { - TreeNodeMock root = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); assertEquals(Collections.emptyList(), root.getIndexedPathFromRoot()); } @Test public void getIndexedPathFromRootSimplePath() { - assertEquals(Arrays.asList(1, 0), getNodeInSimpleTree().getIndexedPathFromRoot()); + assertEquals(Arrays.asList(1, 0), TreeNodeTestData.getNodeInSimpleTree().getIndexedPathFromRoot()); } @Test public void getIndexedPathFromRootComplexPath() { - assertEquals(Arrays.asList(2, 1, 0), getNodeInComplexTree().getIndexedPathFromRoot()); + assertEquals(Arrays.asList(2, 1, 0), TreeNodeTestData.getNodeInComplexTree().getIndexedPathFromRoot()); } @Test public void getDescendantSimplePath() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeInSimpleTree(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeInSimpleTree(root); assertEquals(node, root.getDescendant(Arrays.asList(1, 0)).get()); } @Test public void getDescendantComplexPath() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeInComplexTree(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeInComplexTree(root); assertEquals(node, root.getDescendant(Arrays.asList(2, 1, 0)).get()); } @Test public void getDescendantNonExistentReturnsEmpty() { - TreeNodeMock root = new TreeNodeMock(); - getNodeInComplexTree(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.getNodeInComplexTree(root); assertEquals(Optional.empty(), root.getDescendant(Arrays.asList(1, 100, 0))); } @Test(expected = UnsupportedOperationException.class) public void getPositionInParentForRootThrowsException() { - TreeNodeMock root = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); root.getPositionInParent(); } @Test public void getPositionInParentSimpleTree() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeAsChild(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeAsChild(root); assertEquals(2, node.getPositionInParent()); } @Test public void getIndexOfNonExistentChildReturnsEmpty() { - TreeNodeMock root = new TreeNodeMock(); - assertEquals(Optional.empty(), root.getIndexOfChild(new TreeNodeMock())); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + assertEquals(Optional.empty(), root.getIndexOfChild(new TreeNodeTestData.TreeNodeMock())); } @Test public void getIndexOfChild() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeAsChild(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeAsChild(root); assertEquals((Integer)2, root.getIndexOfChild(node).get()); } @Test public void getLevelOfRoot() { - TreeNodeMock root = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); assertEquals(0, root.getLevel()); } @Test public void getLevelInSimpleTree() { - assertEquals(2, getNodeInSimpleTree().getLevel()); + assertEquals(2, TreeNodeTestData.getNodeInSimpleTree().getLevel()); } @Test public void getLevelInComplexTree() { - assertEquals(3, getNodeInComplexTree().getLevel()); + assertEquals(3, TreeNodeTestData.getNodeInComplexTree().getLevel()); } @Test public void getChildCountInSimpleTree() { - TreeNodeMock root = new TreeNodeMock(); - getNodeInSimpleTree(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.getNodeInSimpleTree(root); assertEquals(2, root.getNumberOfChildren()); } @Test public void getChildCountInComplexTree() { - TreeNodeMock root = new TreeNodeMock(); - getNodeInComplexTree(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.getNodeInComplexTree(root); assertEquals(4, root.getNumberOfChildren()); } @Test public void moveToAddsAsLastChildInSimpleTree() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeInSimpleTree(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeInSimpleTree(root); node.moveTo(root); assertEquals((Integer)2, root.getIndexOfChild(node).get()); } @Test public void moveToAddsAsLastChildInComplexTree() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeInComplexTree(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeInComplexTree(root); node.moveTo(root); assertEquals((Integer)4, root.getIndexOfChild(node).get()); } @Test public void moveToChangesParent() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeInSimpleTree(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeInSimpleTree(root); node.moveTo(root); assertEquals(root, node.getParent().get()); } @Test public void moveToInSameLevelAddsAtEnd() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock child1 = new TreeNodeMock(); - TreeNodeMock child2 = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock child1 = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock child2 = new TreeNodeTestData.TreeNodeMock(); root.addChild(child1); root.addChild(child2); @@ -255,10 +174,10 @@ public void moveToInSameLevelAddsAtEnd() { @Test public void getPathFromRootInSimpleTree() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeInSimpleTree(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeInSimpleTree(root); - List path = node.getPathFromRoot(); + List path = node.getPathFromRoot(); assertEquals(3, path.size()); assertEquals(root, path.get(0)); assertEquals(node, path.get(2)); @@ -266,10 +185,10 @@ public void getPathFromRootInSimpleTree() { @Test public void getPathFromRootInComplexTree() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeInComplexTree(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeInComplexTree(root); - List path = node.getPathFromRoot(); + List path = node.getPathFromRoot(); assertEquals(4, path.size()); assertEquals(root, path.get(0)); assertEquals(node, path.get(3)); @@ -277,149 +196,149 @@ public void getPathFromRootInComplexTree() { @Test public void getPreviousSiblingReturnsCorrect() { - TreeNodeMock root = new TreeNodeMock(); - root.addChild(new TreeNodeMock()); - TreeNodeMock previous = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + root.addChild(new TreeNodeTestData.TreeNodeMock()); + TreeNodeTestData.TreeNodeMock previous = new TreeNodeTestData.TreeNodeMock(); root.addChild(previous); - TreeNodeMock node = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = new TreeNodeTestData.TreeNodeMock(); root.addChild(node); - root.addChild(new TreeNodeMock()); + root.addChild(new TreeNodeTestData.TreeNodeMock()); assertEquals(previous, node.getPreviousSibling().get()); } @Test public void getPreviousSiblingForRootReturnsEmpty() { - TreeNodeMock root = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); assertEquals(Optional.empty(), root.getPreviousSibling()); } @Test public void getPreviousSiblingForNonexistentReturnsEmpty() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = new TreeNodeTestData.TreeNodeMock(); root.addChild(node); assertEquals(Optional.empty(), node.getPreviousSibling()); } @Test public void getNextSiblingReturnsCorrect() { - TreeNodeMock root = new TreeNodeMock(); - root.addChild(new TreeNodeMock()); - TreeNodeMock node = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + root.addChild(new TreeNodeTestData.TreeNodeMock()); + TreeNodeTestData.TreeNodeMock node = new TreeNodeTestData.TreeNodeMock(); root.addChild(node); - TreeNodeMock next = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock next = new TreeNodeTestData.TreeNodeMock(); root.addChild(next); - root.addChild(new TreeNodeMock()); + root.addChild(new TreeNodeTestData.TreeNodeMock()); assertEquals(next, node.getNextSibling().get()); } @Test public void getNextSiblingForRootReturnsEmpty() { - TreeNodeMock root = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); assertEquals(Optional.empty(), root.getNextSibling()); } @Test public void getNextSiblingForNonexistentReturnsEmpty() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = new TreeNodeTestData.TreeNodeMock(); root.addChild(node); assertEquals(Optional.empty(), node.getPreviousSibling()); } @Test public void getParentReturnsCorrect() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeAsChild(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeAsChild(root); assertEquals(root, node.getParent().get()); } @Test public void getParentForRootReturnsEmpty() { - TreeNodeMock root = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); assertEquals(Optional.empty(), root.getParent()); } @Test public void getChildAtReturnsCorrect() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeAsChild(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeAsChild(root); assertEquals(node, root.getChildAt(2).get()); } @Test public void getChildAtInvalidIndexReturnsEmpty() { - TreeNodeMock root = new TreeNodeMock(); - root.addChild(new TreeNodeMock()); - root.addChild(new TreeNodeMock()); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + root.addChild(new TreeNodeTestData.TreeNodeMock()); + root.addChild(new TreeNodeTestData.TreeNodeMock()); assertEquals(Optional.empty(), root.getChildAt(10)); } @Test public void getRootReturnsTrueForRoot() { - TreeNodeMock root = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); assertTrue(root.isRoot()); } @Test public void getRootReturnsFalseForChild() { - assertFalse(getNodeInSimpleTree().isRoot()); + assertFalse(TreeNodeTestData.getNodeInSimpleTree().isRoot()); } @Test public void nodeIsAncestorOfItself() { - TreeNodeMock root = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); assertTrue(root.isAncestorOf(root)); } @Test public void isAncestorOfInSimpleTree() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeInSimpleTree(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeInSimpleTree(root); assertTrue(root.isAncestorOf(node)); } @Test public void isAncestorOfInComplexTree() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeInComplexTree(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeInComplexTree(root); assertTrue(root.isAncestorOf(node)); } @Test public void getRootOfSingleNode() { - TreeNodeMock root = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); assertEquals(root, root.getRoot()); } @Test public void getRootInSimpleTree() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeInSimpleTree(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeInSimpleTree(root); assertEquals(root, node.getRoot()); } @Test public void getRootInComplexTree() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeInComplexTree(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeInComplexTree(root); assertEquals(root, node.getRoot()); } @Test public void isLeafIsCorrectForRootWithoutChildren() { - TreeNodeMock root = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); assertTrue(root.isLeaf()); } @Test public void removeFromParentSetsParentToEmpty() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeAsChild(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeAsChild(root); node.removeFromParent(); assertEquals(Optional.empty(), node.getParent()); @@ -427,8 +346,8 @@ public void removeFromParentSetsParentToEmpty() { @Test public void removeFromParentRemovesNodeFromChildrenCollection() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeAsChild(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeAsChild(root); node.removeFromParent(); assertFalse(root.getChildren().contains(node)); @@ -436,8 +355,8 @@ public void removeFromParentRemovesNodeFromChildrenCollection() { @Test public void removeAllChildrenSetsParentOfChildToEmpty() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeAsChild(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeAsChild(root); root.removeAllChildren(); assertEquals(Optional.empty(), node.getParent()); @@ -445,8 +364,8 @@ public void removeAllChildrenSetsParentOfChildToEmpty() { @Test public void removeAllChildrenRemovesAllNodesFromChildrenCollection() { - TreeNodeMock root = new TreeNodeMock(); - getNodeAsChild(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.getNodeAsChild(root); root.removeAllChildren(); assertEquals(Collections.emptyList(), root.getChildren()); @@ -454,8 +373,8 @@ public void removeAllChildrenRemovesAllNodesFromChildrenCollection() { @Test public void getFirstChildAtReturnsCorrect() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = new TreeNodeTestData.TreeNodeMock(); root.addChild(node); assertEquals(node, root.getFirstChild().get()); @@ -463,30 +382,30 @@ public void getFirstChildAtReturnsCorrect() { @Test public void getFirstChildAtLeafReturnsEmpty() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock leaf = getNodeAsChild(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock leaf = TreeNodeTestData.getNodeAsChild(root); assertEquals(Optional.empty(), leaf.getFirstChild()); } @Test public void isNodeDescendantInFirstLevel() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock child = getNodeAsChild(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock child = TreeNodeTestData.getNodeAsChild(root); assertTrue(root.isNodeDescendant(child)); } @Test public void isNodeDescendantInComplex() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock descendant = getNodeInComplexTree(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock descendant = TreeNodeTestData.getNodeInComplexTree(root); assertTrue(root.isNodeDescendant(descendant)); } @Test public void getChildrenReturnsAllChildren() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock child1 = new TreeNodeMock(); - TreeNodeMock child2 = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock child1 = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock child2 = new TreeNodeTestData.TreeNodeMock(); root.addChild(child1); root.addChild(child2); @@ -495,8 +414,8 @@ public void getChildrenReturnsAllChildren() { @Test public void removeChildSetsParentToEmpty() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeAsChild(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeAsChild(root); root.removeChild(node); assertEquals(Optional.empty(), node.getParent()); @@ -504,8 +423,8 @@ public void removeChildSetsParentToEmpty() { @Test public void removeChildRemovesNodeFromChildrenCollection() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeAsChild(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeAsChild(root); root.removeChild(node); assertFalse(root.getChildren().contains(node)); @@ -513,8 +432,8 @@ public void removeChildRemovesNodeFromChildrenCollection() { @Test public void removeChildIndexSetsParentToEmpty() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeAsChild(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeAsChild(root); root.removeChild(2); assertEquals(Optional.empty(), node.getParent()); @@ -522,8 +441,8 @@ public void removeChildIndexSetsParentToEmpty() { @Test public void removeChildIndexRemovesNodeFromChildrenCollection() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeAsChild(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeAsChild(root); root.removeChild(2); assertFalse(root.getChildren().contains(node)); @@ -531,18 +450,18 @@ public void removeChildIndexRemovesNodeFromChildrenCollection() { @Test(expected = UnsupportedOperationException.class) public void addThrowsExceptionIfNodeHasParent() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeAsChild(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeAsChild(root); root.addChild(node); } @Test public void moveAllChildrenToAddsAtSpecifiedPosition() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = new TreeNodeTestData.TreeNodeMock(); root.addChild(node); - TreeNodeMock child1 = new TreeNodeMock(); - TreeNodeMock child2 = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock child1 = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock child2 = new TreeNodeTestData.TreeNodeMock(); node.addChild(child1); node.addChild(child2); @@ -552,11 +471,11 @@ public void moveAllChildrenToAddsAtSpecifiedPosition() { @Test public void moveAllChildrenToChangesParent() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = new TreeNodeTestData.TreeNodeMock(); root.addChild(node); - TreeNodeMock child1 = new TreeNodeMock(); - TreeNodeMock child2 = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock child1 = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock child2 = new TreeNodeTestData.TreeNodeMock(); node.addChild(child1); node.addChild(child2); @@ -567,18 +486,18 @@ public void moveAllChildrenToChangesParent() { @Test(expected = UnsupportedOperationException.class) public void moveAllChildrenToDescendantThrowsException() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeAsChild(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeAsChild(root); root.moveAllChildrenTo(node, 0); } @Test public void sortChildrenSortsInFirstLevel() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock child1 = new TreeNodeMock("a"); - TreeNodeMock child2 = new TreeNodeMock("b"); - TreeNodeMock child3 = new TreeNodeMock("c"); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock child1 = new TreeNodeTestData.TreeNodeMock("a"); + TreeNodeTestData.TreeNodeMock child2 = new TreeNodeTestData.TreeNodeMock("b"); + TreeNodeTestData.TreeNodeMock child3 = new TreeNodeTestData.TreeNodeMock("c"); root.addChild(child2); root.addChild(child3); root.addChild(child1); @@ -589,11 +508,11 @@ public void sortChildrenSortsInFirstLevel() { @Test public void sortChildrenRecursiveSortsInDeeperLevel() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeInSimpleTree(root); - TreeNodeMock child1 = new TreeNodeMock("a"); - TreeNodeMock child2 = new TreeNodeMock("b"); - TreeNodeMock child3 = new TreeNodeMock("c"); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeInSimpleTree(root); + TreeNodeTestData.TreeNodeMock child1 = new TreeNodeTestData.TreeNodeMock("a"); + TreeNodeTestData.TreeNodeMock child2 = new TreeNodeTestData.TreeNodeMock("b"); + TreeNodeTestData.TreeNodeMock child3 = new TreeNodeTestData.TreeNodeMock("c"); node.addChild(child2); node.addChild(child3); node.addChild(child1); @@ -604,10 +523,10 @@ public void sortChildrenRecursiveSortsInDeeperLevel() { @Test public void copySubtreeCopiesChildren() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeAsChild(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeAsChild(root); - TreeNodeMock copiedRoot = root.copySubtree(); + TreeNodeTestData.TreeNodeMock copiedRoot = root.copySubtree(); assertEquals(Optional.empty(), copiedRoot.getParent()); assertFalse(copiedRoot.getChildren().contains(node)); assertEquals(root.getNumberOfChildren(), copiedRoot.getNumberOfChildren()); @@ -615,20 +534,20 @@ public void copySubtreeCopiesChildren() { @Test public void addChildSomewhereInTreeInvokesChangeEvent() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeInComplexTree(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeInComplexTree(root); root.subscribeToDescendantChanged(subscriber); - node.addChild(new TreeNodeMock()); + node.addChild(new TreeNodeTestData.TreeNodeMock()); verify(subscriber).accept(node); } @Test public void moveNodeSomewhereInTreeInvokesChangeEvent() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeInComplexTree(root); - TreeNodeMock oldParent = node.getParent().get(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeInComplexTree(root); + TreeNodeTestData.TreeNodeMock oldParent = node.getParent().get(); root.subscribeToDescendantChanged(subscriber); @@ -639,9 +558,9 @@ public void moveNodeSomewhereInTreeInvokesChangeEvent() { @Test public void removeChildSomewhereInTreeInvokesChangeEvent() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeInComplexTree(root); - TreeNodeMock child = node.addChild(new TreeNodeMock()); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeInComplexTree(root); + TreeNodeTestData.TreeNodeMock child = node.addChild(new TreeNodeTestData.TreeNodeMock()); root.subscribeToDescendantChanged(subscriber); @@ -651,9 +570,9 @@ public void removeChildSomewhereInTreeInvokesChangeEvent() { @Test public void removeChildIndexSomewhereInTreeInvokesChangeEvent() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeInComplexTree(root); - node.addChild(new TreeNodeMock()); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeInComplexTree(root); + node.addChild(new TreeNodeTestData.TreeNodeMock()); root.subscribeToDescendantChanged(subscriber); @@ -661,49 +580,16 @@ public void removeChildIndexSomewhereInTreeInvokesChangeEvent() { verify(subscriber).accept(node); } - /** - * This is just a dummy class deriving from TreeNode so that we can test the generic class - */ - private static class TreeNodeMock extends TreeNode { - - private final String name; - - public TreeNodeMock() { - this(""); - } - - public TreeNodeMock(String name) { - super(TreeNodeMock.class); - this.name = name; - } - - public String getName() { - return name; - } - - @Override - public String toString() { - return "TreeNodeMock{" + - "name='" + name + '\'' + - '}'; - } - - @Override - public TreeNodeMock copyNode() { - return new TreeNodeMock(name); - } - } - - private static class WrongTreeNodeImplementation extends TreeNode { + private static class WrongTreeNodeImplementation extends TreeNode { // This class is a wrong derived class of TreeNode // since it does not extends TreeNode // See test constructorChecksThatClassImplementsCorrectInterface public WrongTreeNodeImplementation() { - super(TreeNodeMock.class); + super(TreeNodeTestData.TreeNodeMock.class); } @Override - public TreeNodeMock copyNode() { + public TreeNodeTestData.TreeNodeMock copyNode() { return null; } } diff --git a/src/test/java/org/jabref/model/TreeNodeTestData.java b/src/test/java/org/jabref/model/TreeNodeTestData.java new file mode 100644 index 00000000000..939e9629292 --- /dev/null +++ b/src/test/java/org/jabref/model/TreeNodeTestData.java @@ -0,0 +1,121 @@ +package org.jabref.model; + +public class TreeNodeTestData { + /** + * Gets the marked node in the following tree: + * Root + * A + * A (= parent) + * B (<-- this) + */ + public static TreeNodeMock getNodeInSimpleTree(TreeNodeMock root) { + root.addChild(new TreeNodeMock()); + TreeNodeMock parent = new TreeNodeMock(); + root.addChild(parent); + TreeNodeMock node = new TreeNodeMock(); + parent.addChild(node); + return node; + } + + public static TreeNodeMock getNodeInSimpleTree() { + return getNodeInSimpleTree(new TreeNodeMock()); + } + + /** + * Gets the marked node in the following tree: + * Root + * A + * A + * A (= grand parent) + * B + * B (= parent) + * C (<-- this) + * D (= child) + * C + * C + * C + * B + * B + * A + */ + public static TreeNodeMock getNodeInComplexTree(TreeNodeMock root) { + root.addChild(new TreeNodeMock()); + root.addChild(new TreeNodeMock()); + TreeNodeMock grandParent = new TreeNodeMock(); + root.addChild(grandParent); + root.addChild(new TreeNodeMock()); + + grandParent.addChild(new TreeNodeMock()); + TreeNodeMock parent = new TreeNodeMock(); + grandParent.addChild(parent); + grandParent.addChild(new TreeNodeMock()); + grandParent.addChild(new TreeNodeMock()); + + TreeNodeMock node = new TreeNodeMock(); + parent.addChild(node); + parent.addChild(new TreeNodeMock()); + parent.addChild(new TreeNodeMock()); + parent.addChild(new TreeNodeMock()); + + node.addChild(new TreeNodeMock()); + return node; + } + + public static TreeNodeMock getNodeInComplexTree() { + return getNodeInComplexTree(new TreeNodeMock()); + } + + /** + * Gets the marked in the following tree: + * Root + * A + * A + * A (<- this) + * A + */ + public static TreeNodeMock getNodeAsChild(TreeNodeMock root) { + root.addChild(new TreeNodeMock()); + root.addChild(new TreeNodeMock()); + TreeNodeMock node = new TreeNodeMock(); + root.addChild(node); + root.addChild(new TreeNodeMock()); + return node; + } + + /** + * This is just a dummy class deriving from TreeNode so that we can test the generic class + */ + public static class TreeNodeMock extends TreeNode { + + private String name; + + public TreeNodeMock() { + this(""); + } + + public TreeNodeMock(String name) { + super(TreeNodeMock.class); + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return "TreeNodeMock{" + + "name='" + name + '\'' + + '}'; + } + + @Override + public TreeNodeMock copyNode() { + return new TreeNodeMock(name); + } + } +} diff --git a/src/test/java/org/jabref/model/groups/AutomaticKeywordGroupTest.java b/src/test/java/org/jabref/model/groups/AutomaticKeywordGroupTest.java new file mode 100644 index 00000000000..aa9b80c496e --- /dev/null +++ b/src/test/java/org/jabref/model/groups/AutomaticKeywordGroupTest.java @@ -0,0 +1,24 @@ +package org.jabref.model.groups; + +import java.util.HashSet; +import java.util.Set; + +import org.jabref.model.entry.BibEntry; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class AutomaticKeywordGroupTest { + + @Test + public void createSubgroupsForTwoKeywords() throws Exception { + AutomaticKeywordGroup keywordsGroup = new AutomaticKeywordGroup("Keywords", GroupHierarchyType.INDEPENDENT, "keywords", ','); + BibEntry entry = new BibEntry().withField("keywords", "A, B"); + + Set expected = new HashSet<>(); + expected.add(GroupTreeNode.fromGroup(new WordKeywordGroup("A", GroupHierarchyType.INDEPENDENT, "keywords", "A", true, ',', true))); + expected.add(GroupTreeNode.fromGroup(new WordKeywordGroup("B", GroupHierarchyType.INDEPENDENT, "keywords", "B", true, ',', true))); + assertEquals(expected, keywordsGroup.createSubgroups(entry)); + } +} diff --git a/src/test/resources/org/jabref/logic/importer/empty.pdf b/src/test/resources/org/jabref/logic/net/empty.pdf similarity index 100% rename from src/test/resources/org/jabref/logic/importer/empty.pdf rename to src/test/resources/org/jabref/logic/net/empty.pdf