Skip to content

Commit

Permalink
Towards hierarchical keywords (#1950)
Browse files Browse the repository at this point in the history
* Small code cleanup in SpecialFieldsUtils

* Refactor code related to keywords

* Move StringUtil to model and remove EntryUtil

* Add a few more tests

* Change keyword delemiter in groups to Character

* Optimize imports

* Removed unused keyword separator in shareddb ui manager

* Fix build errors

* Fix failing architecture tests

* Reformat imports

* Small renamings

* Move from Inheritance to composition in KeywordList

* Fix tests

* ArXiv accepts import format preferences instead of keyword delimiter

* Fix binding

* Fix arXiv tests
  • Loading branch information
tobiasdiez authored Sep 24, 2016
1 parent 03a1e46 commit 6ae86ae
Show file tree
Hide file tree
Showing 86 changed files with 1,104 additions and 672 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public void setUp() throws SQLException, DatabaseNotSupportedException {
BibDatabaseContext context = new BibDatabaseContext(bibDatabase);


dbmsSynchronizer = new DBMSSynchronizer(context, ", ");
dbmsSynchronizer = new DBMSSynchronizer(context, ',');
dbmsProcessor = DBMSProcessor.getProcessorInstance(dbmsConnection);

bibDatabase.registerListener(dbmsSynchronizer);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ public class SynchronizationTestSimulator {
public void setUp() throws SQLException, DatabaseNotSupportedException {
this.dbmsConnection = TestConnector.getTestDBMSConnection(dbmsType);

clientContextA = new BibDatabaseContext(new Defaults(BibDatabaseMode.BIBTEX), DatabaseLocation.SHARED, ", ");
clientContextA = new BibDatabaseContext(new Defaults(BibDatabaseMode.BIBTEX), DatabaseLocation.SHARED, ',');
clientContextA.getDBMSSynchronizer().openSharedDatabase(dbmsConnection);

clientContextB = new BibDatabaseContext(new Defaults(BibDatabaseMode.BIBTEX), DatabaseLocation.SHARED, ", ");
clientContextB = new BibDatabaseContext(new Defaults(BibDatabaseMode.BIBTEX), DatabaseLocation.SHARED, ',');
clientContextB.getDBMSSynchronizer().openSharedDatabase(dbmsConnection);
eventListenerB = new SynchronizationTestEventListener();
clientContextB.getDBMSSynchronizer().registerListener(eventListenerB);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import java.util.Arrays;
import java.util.Collection;

import net.sf.jabref.model.entry.EntryUtil;
import net.sf.jabref.model.strings.StringUtil;

import org.assertj.swing.fixture.JTableFixture;
import org.junit.Ignore;
Expand Down Expand Up @@ -31,7 +31,7 @@ public void addEntryOfGivenType() {
JTableFixture entryTable = mainFrame.table();

entryTable.requireRowCount(0);
mainFrame.menuItemWithPath("BibTeX", "New entry by type...", EntryUtil.capitalizeFirst(entryType)).click();
mainFrame.menuItemWithPath("BibTeX", "New entry by type...", StringUtil.capitalizeFirst(entryType)).click();
entryTable.requireRowCount(1);
}

Expand Down
2 changes: 1 addition & 1 deletion src/jmh/java/net/sf/jabref/benchmarks/Benchmarks.java
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ public String htmlToLatexConversion() {
@Benchmark
public boolean keywordGroupContains() throws ParseException {
KeywordGroup group = new KeywordGroup("testGroup", "keyword", "testkeyword", false, false,
GroupHierarchyType.INDEPENDENT, ", ");
GroupHierarchyType.INDEPENDENT, ',');
return group.containsAll(database.getEntries());
}

Expand Down
4 changes: 1 addition & 3 deletions src/main/java/net/sf/jabref/JabRefGUI.java
Original file line number Diff line number Diff line change
Expand Up @@ -200,10 +200,8 @@ private void openWindow() {
boolean isSharedDatabaseEdited = Globals.prefs.getBoolean(JabRefPreferences.SHARED_DATABASE_LAST_EDITED);
if (isSharedDatabaseEdited) {
boolean isFocused = Globals.prefs.getBoolean(JabRefPreferences.SHARED_DATABASE_LAST_FOCUSED);
String keywordSeparator = Globals.prefs.get(JabRefPreferences.KEYWORD_SEPARATOR);

try {
new SharedDatabaseUIManager(mainFrame, keywordSeparator).openLastSharedDatabaseTab(isFocused);
new SharedDatabaseUIManager(mainFrame).openLastSharedDatabaseTab(isFocused);
} catch (SQLException | DatabaseNotSupportedException e) {
LOGGER.info("Failed to restore shared database. Use connection dialog to connect.");
new OpenSharedDatabaseDialog(mainFrame).setVisible(true);
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/net/sf/jabref/gui/BasePanel.java
Original file line number Diff line number Diff line change
Expand Up @@ -2397,7 +2397,8 @@ public Optional<String> searchAndOpen() {
Map<BibEntry, List<File>> result;
if (Globals.prefs.getBoolean(JabRefPreferences.AUTOLINK_USE_REG_EXP_SEARCH_KEY)) {
String regExp = Globals.prefs.get(JabRefPreferences.REG_EXP_SEARCH_EXPRESSION_KEY);
result = RegExpFileSearch.findFilesForSet(entries, extensions, dirs, regExp);
result = RegExpFileSearch.findFilesForSet(entries, extensions, dirs, regExp,
Globals.prefs.getKeywordDelimiter());
} else {
boolean autoLinkExactKeyOnly = Globals.prefs.getBoolean(JabRefPreferences.AUTOLINK_EXACT_KEY_ONLY);
result = FileUtil.findAssociatedFiles(entries, extensions, dirs, autoLinkExactKeyOnly);
Expand Down
12 changes: 6 additions & 6 deletions src/main/java/net/sf/jabref/gui/EntryCustomizationDialog.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@
import net.sf.jabref.model.entry.BibEntry;
import net.sf.jabref.model.entry.CustomEntryType;
import net.sf.jabref.model.entry.EntryType;
import net.sf.jabref.model.entry.EntryUtil;
import net.sf.jabref.model.entry.InternalBibtexFields;
import net.sf.jabref.model.strings.StringUtil;

import com.jgoodies.forms.builder.ButtonBarBuilder;

Expand Down Expand Up @@ -259,7 +259,7 @@ private void applyChanges() {

if (defaulted.contains(stringListEntry.getKey())) {
// This type should be reverted to its default setup.
String nm = EntryUtil.capitalizeFirst(stringListEntry.getKey());
String nm = StringUtil.capitalizeFirst(stringListEntry.getKey());
EntryTypes.removeType(nm, bibDatabaseMode);

updateTypesForEntries(nm);
Expand All @@ -284,8 +284,8 @@ private void applyChanges() {

if (changesMade) {
CustomEntryType typ = biblatexMode ?
new CustomEntryType(EntryUtil.capitalizeFirst(stringListEntry.getKey()), reqStr, optStr, opt2Str) :
new CustomEntryType(EntryUtil.capitalizeFirst(stringListEntry.getKey()), reqStr, optStr);
new CustomEntryType(StringUtil.capitalizeFirst(stringListEntry.getKey()), reqStr, optStr, opt2Str) :
new CustomEntryType(StringUtil.capitalizeFirst(stringListEntry.getKey()), reqStr, optStr);

EntryTypes.addOrModifyCustomEntryType(typ);
updateTypesForEntries(typ.getName());
Expand Down Expand Up @@ -319,14 +319,14 @@ private void typeDeletion(String name) {
+ "type will be declared "
+ "typeless. Continue?"),
Localization.lang("Delete custom format") +
" '" + EntryUtil.capitalizeFirst(name) + '\'', JOptionPane.YES_NO_OPTION,
" '" + StringUtil.capitalizeFirst(name) + '\'', JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE);
if (reply != JOptionPane.YES_OPTION) {
return;
}
}
EntryTypes.removeType(name, bibDatabaseMode);
updateTypesForEntries(EntryUtil.capitalizeFirst(name));
updateTypesForEntries(StringUtil.capitalizeFirst(name));
changed.remove(name);
reqLists.remove(name);
optLists.remove(name);
Expand Down
112 changes: 55 additions & 57 deletions src/main/java/net/sf/jabref/gui/actions/ManageKeywordsAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,8 @@
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;

import javax.swing.AbstractAction;
import javax.swing.Action;
Expand Down Expand Up @@ -40,7 +37,9 @@
import net.sf.jabref.model.FieldChange;
import net.sf.jabref.model.entry.BibEntry;
import net.sf.jabref.model.entry.FieldName;
import net.sf.jabref.preferences.JabRefPreferences;
import net.sf.jabref.model.entry.Keyword;
import net.sf.jabref.model.entry.KeywordList;
import net.sf.jabref.model.strings.StringUtil;
import net.sf.jabref.specialfields.Printed;
import net.sf.jabref.specialfields.Priority;
import net.sf.jabref.specialfields.Quality;
Expand All @@ -64,14 +63,14 @@ public class ManageKeywordsAction extends MnemonicAwareAction {
private JDialog diag;


private DefaultListModel<String> keywordListModel;
private DefaultListModel<Keyword> keywordListModel;

private JRadioButton intersectKeywords;
private JRadioButton mergeKeywords;

private boolean canceled;

private final Set<String> sortedKeywordsOfAllEntriesBeforeUpdateByUser = new TreeSet<>();
private final KeywordList sortedKeywordsOfAllEntriesBeforeUpdateByUser = new KeywordList();


public ManageKeywordsAction(JabRefFrame frame) {
Expand All @@ -87,7 +86,7 @@ private void createDialog() {
JTextField keyword = new JTextField();

keywordListModel = new DefaultListModel<>();
JList<String> keywordList = new JList<>(keywordListModel);
JList<Keyword> keywordList = new JList<>(keywordListModel);
keywordList.setVisibleRowCount(8);
JScrollPane kPane = new JScrollPane(keywordList);

Expand Down Expand Up @@ -149,9 +148,9 @@ public void actionPerformed(ActionEvent e) {

final ActionListener removeActionListenter = arg0 -> {
// keywordList.getSelectedIndices(); does not work, therefore we operate on the values
List<String> values = keywordList.getSelectedValuesList();
List<Keyword> values = keywordList.getSelectedValuesList();

for (String val : values) {
for (Keyword val : values) {
keywordListModel.removeElement(val);
}
};
Expand Down Expand Up @@ -213,29 +212,33 @@ public void keyPressed(KeyEvent e) {
diag.getContentPane().add(bb.getPanel(), BorderLayout.SOUTH);
}

private void addButtonActionListener(JTextField keyword) {
String text = keyword.getText().trim();
if (!text.isEmpty()) {
if (keywordListModel.isEmpty()) {
keywordListModel.addElement(text);
private void addButtonActionListener(JTextField keywordTextField) {
if (StringUtil.isBlank(keywordTextField.getText())) {
return; // nothing to add
}


Keyword newKeyword = new Keyword(keywordTextField.getText().trim());
if (keywordListModel.isEmpty()) {
keywordListModel.addElement(newKeyword);
} else {
int idx = 0;
Keyword element = keywordListModel.getElementAt(idx);
while ((idx < keywordListModel.size()) && (element.compareTo(newKeyword) < 0)) {
idx++;
}
if (idx == keywordListModel.size()) {
// list is empty or word is greater than last word in list
keywordListModel.addElement(newKeyword);
} else if (element.compareTo(newKeyword) == 0) {
// nothing to do, word already in table
} else {
int idx = 0;
String element = keywordListModel.getElementAt(idx);
while ((idx < keywordListModel.size()) && (element.compareTo(text) < 0)) {
idx++;
}
if (idx == keywordListModel.size()) {
// list is empty or word is greater than last word in list
keywordListModel.addElement(text);
} else if (element.compareTo(text) == 0) {
// nothing to do, word already in table
} else {
keywordListModel.add(idx, text);
}
keywordListModel.add(idx, newKeyword);
}
keyword.setText(null);
keyword.requestFocusInWindow();
}
keywordTextField.setText(null);
keywordTextField.requestFocusInWindow();

}

@Override
Expand Down Expand Up @@ -263,19 +266,19 @@ public void actionPerformed(ActionEvent e) {
return;
}

Set<String> keywordsToAdd = new HashSet<>();
Set<String> userSelectedKeywords = new HashSet<>();
KeywordList keywordsToAdd = new KeywordList();
KeywordList userSelectedKeywords = new KeywordList();
// build keywordsToAdd and userSelectedKeywords in parallel
for (Enumeration<String> keywords = keywordListModel.elements(); keywords.hasMoreElements();) {
String keyword = keywords.nextElement();
for (Enumeration<Keyword> keywords = keywordListModel.elements(); keywords.hasMoreElements();) {
Keyword keyword = keywords.nextElement();
userSelectedKeywords.add(keyword);
if (!sortedKeywordsOfAllEntriesBeforeUpdateByUser.contains(keyword)) {
keywordsToAdd.add(keyword);
}
}

Set<String> keywordsToRemove = new HashSet<>();
for (String kword : sortedKeywordsOfAllEntriesBeforeUpdateByUser) {
KeywordList keywordsToRemove = new KeywordList();
for (Keyword kword : sortedKeywordsOfAllEntriesBeforeUpdateByUser) {
if (!userSelectedKeywords.contains(kword)) {
keywordsToRemove.add(kword);
}
Expand All @@ -295,19 +298,18 @@ public void actionPerformed(ActionEvent e) {
bp.markBaseChanged();
}

private NamedCompound updateKeywords(List<BibEntry> entries, Set<String> keywordsToAdd,
Set<String> keywordsToRemove) {
private NamedCompound updateKeywords(List<BibEntry> entries, KeywordList keywordsToAdd,
KeywordList keywordsToRemove) {
NamedCompound ce = new NamedCompound(Localization.lang("Update keywords"));
for (BibEntry entry : entries) {
Set<String> keywords = entry.getKeywords();
KeywordList keywords = entry.getKeywords(Globals.prefs.getKeywordDelimiter());

// update keywords
keywords.removeAll(keywordsToRemove);
keywords.addAll(keywordsToAdd);

// put keywords back
Optional<FieldChange> change = entry.putKeywords(keywords,
Globals.prefs.get(JabRefPreferences.KEYWORD_SEPARATOR));
Optional<FieldChange> change = entry.putKeywords(keywords, Globals.prefs.getKeywordDelimiter());
if (change.isPresent()) {
ce.addEdit(new UndoableFieldChange(change.get()));
}
Expand All @@ -320,61 +322,57 @@ private NamedCompound updateKeywords(List<BibEntry> entries, Set<String> keyword
return ce;
}

private void synchronizeSpecialFields(Set<String> keywordsToAdd, Set<String> keywordsToRemove) {
private void synchronizeSpecialFields(KeywordList keywordsToAdd, KeywordList keywordsToRemove) {
// we need to check whether a special field is added
// for each field:
// check if something is added
// if yes, add all keywords of that special fields to the keywords to be removed

Set<String> clone;
KeywordList clone;

// Priority
clone = createClone(keywordsToAdd);
clone = keywordsToAdd.createClone();
clone.retainAll(Priority.getInstance().getKeyWords());
if (!clone.isEmpty()) {
keywordsToRemove.addAll(Priority.getInstance().getKeyWords());
}

// Quality
clone = createClone(keywordsToAdd);
clone = keywordsToAdd.createClone();
clone.retainAll(Quality.getInstance().getKeyWords());
if (!clone.isEmpty()) {
keywordsToRemove.addAll(Quality.getInstance().getKeyWords());
}

// Rank
clone = createClone(keywordsToAdd);
clone = keywordsToAdd.createClone();
clone.retainAll(Rank.getInstance().getKeyWords());
if (!clone.isEmpty()) {
keywordsToRemove.addAll(Rank.getInstance().getKeyWords());
}

// Relevance
clone = createClone(keywordsToAdd);
clone = keywordsToAdd.createClone();
clone.retainAll(Relevance.getInstance().getKeyWords());
if (!clone.isEmpty()) {
keywordsToRemove.addAll(Relevance.getInstance().getKeyWords());
}

// Read status
clone = createClone(keywordsToAdd);
clone = keywordsToAdd.createClone();
clone.retainAll(ReadStatus.getInstance().getKeyWords());
if (!clone.isEmpty()) {
keywordsToRemove.addAll(ReadStatus.getInstance().getKeyWords());
}

// Printed
clone = createClone(keywordsToAdd);
clone = keywordsToAdd.createClone();
clone.retainAll(Printed.getInstance().getKeyWords());
if (!clone.isEmpty()) {
keywordsToRemove.addAll(Printed.getInstance().getKeyWords());
}
}

private static Set<String> createClone(Set<String> keywordsToAdd) {
return new HashSet<>(keywordsToAdd);
}

private void fillKeyWordList() {
BasePanel bp = frame.getCurrentBasePanel();
List<BibEntry> entries = bp.getSelectedEntries();
Expand All @@ -385,27 +383,27 @@ private void fillKeyWordList() {

if (mergeKeywords.isSelected()) {
for (BibEntry entry : entries) {
Set<String> separatedKeywords = entry.getKeywords();
KeywordList separatedKeywords = entry.getKeywords(Globals.prefs.getKeywordDelimiter());
sortedKeywordsOfAllEntriesBeforeUpdateByUser.addAll(separatedKeywords);
}
} else {
assert intersectKeywords.isSelected();

// all keywords from first entry have to be added
BibEntry firstEntry = entries.get(0);
Set<String> separatedKeywords = firstEntry.getKeywords();
KeywordList separatedKeywords = firstEntry.getKeywords(Globals.prefs.getKeywordDelimiter());
sortedKeywordsOfAllEntriesBeforeUpdateByUser.addAll(separatedKeywords);

// for the remaining entries, intersection has to be used
// this approach ensures that one empty keyword list leads to an empty set of common keywords
for (int i = 1; i < entries.size(); i++) {
BibEntry entry = entries.get(i);
separatedKeywords = entry.getKeywords();
separatedKeywords = entry.getKeywords(Globals.prefs.getKeywordDelimiter());
sortedKeywordsOfAllEntriesBeforeUpdateByUser.retainAll(separatedKeywords);
}
}
for (String s : sortedKeywordsOfAllEntriesBeforeUpdateByUser) {
keywordListModel.addElement(s);
for (Keyword keyword : sortedKeywordsOfAllEntriesBeforeUpdateByUser) {
keywordListModel.addElement(keyword);
}
}

Expand Down
Loading

0 comments on commit 6ae86ae

Please sign in to comment.