diff --git a/src/main/java/net/sf/jabref/exporter/layout/format/RisKeywords.java b/src/main/java/net/sf/jabref/exporter/layout/format/RisKeywords.java index c3a383ec7beb..a72785622c0c 100644 --- a/src/main/java/net/sf/jabref/exporter/layout/format/RisKeywords.java +++ b/src/main/java/net/sf/jabref/exporter/layout/format/RisKeywords.java @@ -19,7 +19,6 @@ import net.sf.jabref.*; import net.sf.jabref.exporter.layout.*; -import net.sf.jabref.util.Util; public class RisKeywords implements LayoutFormatter { @@ -29,11 +28,11 @@ public String format(String s) { return ""; } StringBuilder sb = new StringBuilder(); - List keywords = Util.getSeparatedKeywords(s); + List keywords = net.sf.jabref.model.entry.EntryUtil.getSeparatedKeywords(s); for (int i = 0; i < keywords.size(); i++) { sb.append("KW - "); sb.append(keywords.get(i)); - if (i < keywords.size() - 1) { + if (i < (keywords.size() - 1)) { sb.append(Globals.NEWLINE); } } diff --git a/src/main/java/net/sf/jabref/logic/labelPattern/LabelPatternUtil.java b/src/main/java/net/sf/jabref/logic/labelPattern/LabelPatternUtil.java index 50c3d0af4c70..ce1f7b1a94bb 100644 --- a/src/main/java/net/sf/jabref/logic/labelPattern/LabelPatternUtil.java +++ b/src/main/java/net/sf/jabref/logic/labelPattern/LabelPatternUtil.java @@ -745,7 +745,7 @@ else if (val.matches("edtr\\d+")) { } else if (val.matches("keyword\\d+")) { // according to LabelPattern.php, it returns keyword number n int num = Integer.parseInt(val.substring(7)); - ArrayList separatedKeywords = Util.getSeparatedKeywords(entry); + ArrayList separatedKeywords = entry.getSeparatedKeywords(); if (separatedKeywords.size() < num) { // not enough keywords return ""; @@ -761,7 +761,7 @@ else if (val.matches("edtr\\d+")) { } else { num = Integer.MAX_VALUE; } - ArrayList separatedKeywords = Util.getSeparatedKeywords(entry); + ArrayList separatedKeywords = entry.getSeparatedKeywords(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < Math.min(separatedKeywords.size(), num); i++) { String keyword = separatedKeywords.get(i); diff --git a/src/main/java/net/sf/jabref/model/entry/BibtexEntry.java b/src/main/java/net/sf/jabref/model/entry/BibtexEntry.java index fa1637c55188..ca87aead0de2 100644 --- a/src/main/java/net/sf/jabref/model/entry/BibtexEntry.java +++ b/src/main/java/net/sf/jabref/model/entry/BibtexEntry.java @@ -24,6 +24,7 @@ import java.text.ParseException; import java.text.ParsePosition; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.HashMap; @@ -484,4 +485,70 @@ public String getPublicationDate() { } return year; } + + public void putKeywords(ArrayList keywords) { + // Set Keyword Field + String oldValue = this.getField("keywords"); + String newValue; + if (!keywords.isEmpty()) { + StringBuilder sb = new StringBuilder(); + for (String keyword : keywords) { + sb.append(keyword); + sb.append(", "); + } + sb.delete(sb.length() - 2, sb.length()); + newValue = sb.toString(); + } else { + newValue = null; + } + if ((oldValue == null) && (newValue == null)) { + return; + } + if ((oldValue == null) || !oldValue.equals(newValue)) { + this.setField("keywords", newValue); + } + } + + /** + * Check if a keyword already exists (case insensitive), if not: add it + * + * @param keyword Keyword to add + */ + public void addKeyword(String keyword) { + ArrayList keywords = this.getSeparatedKeywords(); + Boolean duplicate = false; + + if ((keyword == null) || (keyword.length() == 0)) { + return; + } + + for (String key : keywords) { + if (keyword.equalsIgnoreCase(key)) { + duplicate = true; + break; + } + } + + if (!duplicate) { + keywords.add(keyword); + this.putKeywords(keywords); + } + } + + /** + * Add multiple keywords to entry + * + * @param keywords Keywords to add + */ + public void addKeywords(ArrayList keywords) { + if (keywords != null) { + for (String keyword : keywords) { + this.addKeyword(keyword); + } + } + } + + public ArrayList getSeparatedKeywords() { + return net.sf.jabref.model.entry.EntryUtil.getSeparatedKeywords(this.getField("keywords")); + } } diff --git a/src/main/java/net/sf/jabref/model/entry/EntryUtil.java b/src/main/java/net/sf/jabref/model/entry/EntryUtil.java index 027f28c97878..9483e368b152 100644 --- a/src/main/java/net/sf/jabref/model/entry/EntryUtil.java +++ b/src/main/java/net/sf/jabref/model/entry/EntryUtil.java @@ -2,9 +2,13 @@ import java.util.ArrayList; import java.util.List; +import java.util.StringTokenizer; public class EntryUtil { + public static final String SEPARATING_CHARS_NOSPACE = ";,\n"; + + /** * Static equals that can also return the right result when one of the objects is null. * @@ -57,4 +61,43 @@ public static List getRemainder(List all, List subset) { } return al; } + + /** + * Concatenate two String arrays + * + * @param array1 + * the first string array + * @param array2 + * the second string array + * @return The concatenation of array1 and array2 + */ + public static String[] arrayConcat(String[] array1, String[] array2) { + int len1 = array1.length; + int len2 = array2.length; + String[] union = new String[len1 + len2]; + System.arraycopy(array1, 0, union, 0, len1); + System.arraycopy(array2, 0, union, len1, len2); + return union; + } + + /** + * @param keywords a String of keywords + * @return an ArrayList containing the keywords. An emtpy list if keywords are null or empty + */ + public static ArrayList getSeparatedKeywords(String keywords) { + ArrayList res = new ArrayList<>(); + if (keywords == null) { + return res; + } + // _NOSPACE is a hack to support keywords such as "choreography transactions" + // a more intelligent algorithm would check for the separator chosen (SEPARATING_CHARS_NOSPACE) + // if nothing is found, " " is likely to be the separating char. + // solution by RisKeywords.java: s.split(",[ ]*") + StringTokenizer tok = new StringTokenizer(keywords, SEPARATING_CHARS_NOSPACE); + while (tok.hasMoreTokens()) { + String word = tok.nextToken().trim(); + res.add(word); + } + return res; + } } diff --git a/src/main/java/net/sf/jabref/specialfields/SpecialFieldsUtils.java b/src/main/java/net/sf/jabref/specialfields/SpecialFieldsUtils.java index 3aa9c4a8bd53..adafe81d787a 100644 --- a/src/main/java/net/sf/jabref/specialfields/SpecialFieldsUtils.java +++ b/src/main/java/net/sf/jabref/specialfields/SpecialFieldsUtils.java @@ -1,4 +1,4 @@ -/* Copyright (C) 2012 JabRef contributors. +/* Copyright (C) 2012-2015 JabRef contributors. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or @@ -23,6 +23,7 @@ import net.sf.jabref.model.entry.BibtexEntry; import net.sf.jabref.Globals; import net.sf.jabref.gui.undo.NamedCompound; +import net.sf.jabref.gui.undo.UndoableFieldChange; public class SpecialFieldsUtils { @@ -55,7 +56,7 @@ public class SpecialFieldsUtils { public static final Boolean PREF_SHOWCOLUMN_PRINTED_DEFAULT = Boolean.FALSE; // The choice between PREF_AUTOSYNCSPECIALFIELDSTOKEYWORDS and PREF_SERIALIZESPECIALFIELDS is mutually exclusive - // At least in the settings, not in the implementation. But having both confused the users, therefore, having activated both options at the same time has been disabled + // At least in the settings, not in the implementation. But having both confused the users, therefore, having activated both options at the same time has been disabled public static final String PREF_AUTOSYNCSPECIALFIELDSTOKEYWORDS = "autoSyncSpecialFieldsToKeywords"; public static final Boolean PREF_AUTOSYNCSPECIALFIELDSTOKEYWORDS_DEFAULT = Boolean.TRUE; @@ -90,7 +91,7 @@ private static void exportFieldToKeywords(SpecialField e, String newValue, Bibte if (!SpecialFieldsUtils.keywordSyncEnabled()) { return; } - ArrayList keywordList = Util.getSeparatedKeywords(be); + ArrayList keywordList = be.getSeparatedKeywords(); List values = e.getKeyWords(); int foundPos = -1; @@ -111,12 +112,20 @@ private static void exportFieldToKeywords(SpecialField e, String newValue, Bibte keywordList.add(foundPos, newValue); } } - Util.putKeywords(be, keywordList, ce); + String oldValue = be.getField("keywords"); + be.putKeywords(keywordList); + String updatedValue = be.getField("keywords"); + if ((oldValue == null) || !oldValue.equals(updatedValue)) { + if (ce != null) { + ce.addEdit(new UndoableFieldChange(be, "keywords", oldValue, updatedValue)); + } + } + } /** * Update keywords according to values of special fields - * + * * @param nc indicates the undo named compound. May be null */ public static void syncKeywordsFromSpecialFields(BibtexEntry be, NamedCompound nc) { @@ -142,14 +151,15 @@ private static void importKeywordsForField(ArrayList keywordList, Specia /** * updates field values according to keywords - * + * * @param ce indicates the undo named compound. May be null */ public static void syncSpecialFieldsFromKeywords(BibtexEntry be, NamedCompound ce) { if (be.getField("keywords") == null) { return; } - ArrayList keywordList = Util.getSeparatedKeywords(be.getField("keywords")); + ArrayList keywordList = net.sf.jabref.model.entry.EntryUtil + .getSeparatedKeywords(be.getField("keywords")); SpecialFieldsUtils.importKeywordsForField(keywordList, Priority.getInstance(), be, ce); SpecialFieldsUtils.importKeywordsForField(keywordList, Rank.getInstance(), be, ce); SpecialFieldsUtils.importKeywordsForField(keywordList, Quality.getInstance(), be, ce); diff --git a/src/main/java/net/sf/jabref/util/ManageKeywordsAction.java b/src/main/java/net/sf/jabref/util/ManageKeywordsAction.java index 5d13d6dddb70..fe439790847b 100644 --- a/src/main/java/net/sf/jabref/util/ManageKeywordsAction.java +++ b/src/main/java/net/sf/jabref/util/ManageKeywordsAction.java @@ -59,6 +59,7 @@ import net.sf.jabref.specialfields.SpecialFieldsUtils; import net.sf.jabref.gui.undo.NamedCompound; import net.sf.jabref.gui.util.PositionWindow; +import net.sf.jabref.gui.undo.UndoableFieldChange; import com.jgoodies.forms.builder.ButtonBarBuilder; import com.jgoodies.forms.builder.FormBuilder; @@ -378,7 +379,7 @@ public void actionPerformed(ActionEvent e) { BibtexEntry[] entries = bp.getSelectedEntries(); NamedCompound ce = new NamedCompound(Localization.lang("Update keywords")); for (BibtexEntry entry : entries) { - ArrayList separatedKeywords = Util.getSeparatedKeywords(entry); + ArrayList separatedKeywords = entry.getSeparatedKeywords(); // we "intercept" with a treeset // pro: no duplicates @@ -393,7 +394,12 @@ public void actionPerformed(ActionEvent e) { // put keywords back separatedKeywords.clear(); separatedKeywords.addAll(keywords); - Util.putKeywords(entry, separatedKeywords, ce); + String oldValue = entry.getField("keywords"); + entry.putKeywords(separatedKeywords); + String updatedValue = entry.getField("keywords"); + if ((oldValue == null) || !oldValue.equals(updatedValue)) { + ce.addEdit(new UndoableFieldChange(entry, "keywords", oldValue, updatedValue)); + } if (SpecialFieldsUtils.keywordSyncEnabled()) { SpecialFieldsUtils.syncSpecialFieldsFromKeywords(entry, ce); @@ -419,7 +425,7 @@ private void fillKeyWordList() { if (mergeKeywords.isSelected()) { for (BibtexEntry entry : entries) { - ArrayList separatedKeywords = Util.getSeparatedKeywords(entry); + ArrayList separatedKeywords = entry.getSeparatedKeywords(); sortedKeywordsOfAllEntriesBeforeUpdateByUser.addAll(separatedKeywords); } } else { @@ -427,14 +433,14 @@ private void fillKeyWordList() { // all keywords from first entry have to be added BibtexEntry firstEntry = entries[0]; - ArrayList separatedKeywords = Util.getSeparatedKeywords(firstEntry); + ArrayList separatedKeywords = firstEntry.getSeparatedKeywords(); 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.length; i++) { BibtexEntry entry = entries[i]; - separatedKeywords = Util.getSeparatedKeywords(entry); + separatedKeywords = entry.getSeparatedKeywords(); sortedKeywordsOfAllEntriesBeforeUpdateByUser.retainAll(separatedKeywords); } } diff --git a/src/main/java/net/sf/jabref/util/Util.java b/src/main/java/net/sf/jabref/util/Util.java index efa2cfaefe06..ebae42eff91b 100644 --- a/src/main/java/net/sf/jabref/util/Util.java +++ b/src/main/java/net/sf/jabref/util/Util.java @@ -94,8 +94,6 @@ public class Util { public static final String ARXIV_LOOKUP_PREFIX = "http://arxiv.org/abs/"; - private static final String SEPARATING_CHARS_NOSPACE = ";,\n"; - private static final UnicodeCharMap UNICODE_CHAR_MAP = new UnicodeCharMap(); @@ -688,57 +686,6 @@ public static String getLinkedFileName(BibtexDatabase database, BibtexEntry entr return targetName; } - /** - * @param keywords a String of keywords - * @return an ArrayList containing the keywords. An emtpy list if keywords are null or empty - */ - public static ArrayList getSeparatedKeywords(String keywords) { - ArrayList res = new ArrayList<>(); - if (keywords == null) { - return res; - } - // _NOSPACE is a hack to support keywords such as "choreography transactions" - // a more intelligent algorithm would check for the separator chosen (SEPARATING_CHARS_NOSPACE) - // if nothing is found, " " is likely to be the separating char. - // solution by RisKeywords.java: s.split(",[ ]*") - StringTokenizer tok = new StringTokenizer(keywords, net.sf.jabref.util.Util.SEPARATING_CHARS_NOSPACE); - while (tok.hasMoreTokens()) { - String word = tok.nextToken().trim(); - res.add(word); - } - return res; - } - - public static ArrayList getSeparatedKeywords(BibtexEntry be) { - return net.sf.jabref.util.Util.getSeparatedKeywords(be.getField("keywords")); - } - - public static void putKeywords(BibtexEntry entry, ArrayList keywords, NamedCompound ce) { - // Set Keyword Field - String oldValue = entry.getField("keywords"); - String newValue; - if (!keywords.isEmpty()) { - StringBuilder sb = new StringBuilder(); - for (String keyword : keywords) { - sb.append(keyword); - sb.append(", "); - } - sb.delete(sb.length() - 2, sb.length()); - newValue = sb.toString(); - } else { - newValue = null; - } - if ((oldValue == null) && (newValue == null)) { - return; - } - if ((oldValue == null) || !oldValue.equals(newValue)) { - entry.setField("keywords", newValue); - if (ce != null) { - ce.addEdit(new UndoableFieldChange(entry, "keywords", oldValue, newValue)); - } - } - } - /** * @param ce indicates the undo named compound. May be null */ diff --git a/src/test/java/net/sf/jabref/model/entry/BibtexEntryTests.java b/src/test/java/net/sf/jabref/model/entry/BibtexEntryTests.java index 983971b594b7..a6ad6fc069a8 100644 --- a/src/test/java/net/sf/jabref/model/entry/BibtexEntryTests.java +++ b/src/test/java/net/sf/jabref/model/entry/BibtexEntryTests.java @@ -93,4 +93,36 @@ public void testGetPublicationDate() { .getPublicationDate()); } + + + @Test + public void testKeywordMethods() { + BibtexEntry be = BibtexParser.singleFromString("@ARTICLE{Key15, keywords = {Foo, Bar}}"); + + String[] expected = {"Foo", "Bar"}; + Assert.assertArrayEquals(expected, be.getSeparatedKeywords().toArray()); + + be.addKeyword("FooBar"); + String[] expected2 = {"Foo", "Bar", "FooBar"}; + Assert.assertArrayEquals(expected2, be.getSeparatedKeywords().toArray()); + + be.addKeyword("FooBar"); + Assert.assertArrayEquals(expected2, be.getSeparatedKeywords().toArray()); + + be.addKeyword("FOO"); + Assert.assertArrayEquals(expected2, be.getSeparatedKeywords().toArray()); + + be.addKeyword(""); + Assert.assertArrayEquals(expected2, be.getSeparatedKeywords().toArray()); + + be.addKeyword(null); + Assert.assertArrayEquals(expected2, be.getSeparatedKeywords().toArray()); + + be.addKeywords(null); + Assert.assertArrayEquals(expected2, be.getSeparatedKeywords().toArray()); + + BibtexEntry be2 = new BibtexEntry(); + be2.addKeywords(be.getSeparatedKeywords()); + Assert.assertArrayEquals(expected2, be2.getSeparatedKeywords().toArray()); + } } diff --git a/src/test/java/net/sf/jabref/model/entry/EntryUtilTest.java b/src/test/java/net/sf/jabref/model/entry/EntryUtilTest.java index 914103657aff..c650ade243cd 100644 --- a/src/test/java/net/sf/jabref/model/entry/EntryUtilTest.java +++ b/src/test/java/net/sf/jabref/model/entry/EntryUtilTest.java @@ -1,5 +1,7 @@ package net.sf.jabref.model.entry; +import java.util.ArrayList; + import org.junit.Assert; import org.junit.Test; @@ -12,4 +14,13 @@ public void testNCase() { Assert.assertEquals("A", EntryUtil.capitalizeFirst("a")); Assert.assertEquals("Aa", EntryUtil.capitalizeFirst("AA")); } + + @Test + public void getSeparatedKeywords() { + String keywords = "w1, w2a w2b, w3"; + ArrayList separatedKeywords = EntryUtil.getSeparatedKeywords(keywords); + String[] expected = new String[] {"w1", "w2a w2b", "w3"}; + Assert.assertArrayEquals(expected, separatedKeywords.toArray()); + } + } diff --git a/src/test/java/net/sf/jabref/model/entry/UtilTest.java b/src/test/java/net/sf/jabref/model/entry/UtilTest.java deleted file mode 100644 index e91d88bb1bd9..000000000000 --- a/src/test/java/net/sf/jabref/model/entry/UtilTest.java +++ /dev/null @@ -1,15 +0,0 @@ -package net.sf.jabref.model.entry; - -import org.junit.Assert; -import org.junit.Test; - -public class UtilTest { - - @Test - public void testNCase() { - Assert.assertEquals("", Util.capitalizeFirst("")); - Assert.assertEquals("Hello world", Util.capitalizeFirst("Hello World")); - Assert.assertEquals("A", Util.capitalizeFirst("a")); - Assert.assertEquals("Aa", Util.capitalizeFirst("AA")); - } -} diff --git a/src/test/java/net/sf/jabref/util/UtilTest.java b/src/test/java/net/sf/jabref/util/UtilTest.java index 1f2ad60811a6..fe4e23ca4ef5 100644 --- a/src/test/java/net/sf/jabref/util/UtilTest.java +++ b/src/test/java/net/sf/jabref/util/UtilTest.java @@ -179,13 +179,5 @@ public void testExpandBrackets() { net.sf.jabref.util.Util.expandBrackets("[author] have published [title] in [journal].", entry, database)); } - @Test - public void getSeparatedKeywords() { - String keywords = "w1, w2a w2b, w3"; - ArrayList separatedKeywords = net.sf.jabref.util.Util.getSeparatedKeywords(keywords); - String[] expected = new String[]{"w1", "w2a w2b", "w3"}; - Assert.assertArrayEquals(expected, separatedKeywords.toArray()); - } - }