|
16 | 16 | import java.util.Set;
|
17 | 17 | import java.util.TreeSet;
|
18 | 18 | import java.util.concurrent.ConcurrentHashMap;
|
| 19 | +import java.util.function.Consumer; |
19 | 20 | import java.util.regex.Pattern;
|
20 | 21 | import java.util.stream.Collectors;
|
21 | 22 |
|
|
28 | 29 | import org.jabref.model.entry.BibEntry;
|
29 | 30 | import org.jabref.model.entry.BibtexString;
|
30 | 31 | import org.jabref.model.entry.Month;
|
| 32 | +import org.jabref.model.entry.ParsedEntryLink; |
31 | 33 | import org.jabref.model.entry.event.EntriesEventSource;
|
32 | 34 | import org.jabref.model.entry.event.EntryChangedEvent;
|
33 | 35 | import org.jabref.model.entry.event.FieldChangedEvent;
|
34 | 36 | import org.jabref.model.entry.field.Field;
|
35 | 37 | import org.jabref.model.entry.field.FieldFactory;
|
| 38 | +import org.jabref.model.entry.field.FieldProperty; |
36 | 39 | import org.jabref.model.entry.field.StandardField;
|
37 | 40 | import org.jabref.model.strings.StringUtil;
|
38 | 41 |
|
@@ -61,6 +64,9 @@ public class BibDatabase {
|
61 | 64 | // Not included in equals, because it is not relevant for the content of the database
|
62 | 65 | private final EventBus eventBus = new EventBus();
|
63 | 66 |
|
| 67 | + // Reverse index for citation links |
| 68 | + private final Map<String, Set<BibEntry>> citationIndex = new ConcurrentHashMap<>(); |
| 69 | + |
64 | 70 | private String preamble;
|
65 | 71 |
|
66 | 72 | // All file contents below the last entry in the file
|
@@ -192,7 +198,11 @@ public synchronized void insertEntries(List<BibEntry> newEntries, EntriesEventSo
|
192 | 198 | eventBus.post(new EntriesAddedEvent(newEntries, newEntries.getFirst(), eventSource));
|
193 | 199 | }
|
194 | 200 | entries.addAll(newEntries);
|
195 |
| - newEntries.forEach(entry -> entriesId.put(entry.getId(), entry)); |
| 201 | + newEntries.forEach(entry -> { |
| 202 | + entriesId.put(entry.getId(), entry); |
| 203 | + indexEntry(entry); |
| 204 | + } |
| 205 | + ); |
196 | 206 | }
|
197 | 207 |
|
198 | 208 | public synchronized void removeEntry(BibEntry bibEntry) {
|
@@ -223,17 +233,72 @@ public synchronized void removeEntries(List<BibEntry> toBeDeleted) {
|
223 | 233 | public synchronized void removeEntries(List<BibEntry> toBeDeleted, EntriesEventSource eventSource) {
|
224 | 234 | Objects.requireNonNull(toBeDeleted);
|
225 | 235 |
|
226 |
| - List<String> ids = new ArrayList<>(); |
| 236 | + Collection idsToBeDeleted; |
| 237 | + if (toBeDeleted.size() > 10) { |
| 238 | + idsToBeDeleted = new HashSet<>(); |
| 239 | + } else { |
| 240 | + idsToBeDeleted = new ArrayList<>(toBeDeleted.size()); |
| 241 | + } |
| 242 | + |
227 | 243 | for (BibEntry entry : toBeDeleted) {
|
228 |
| - ids.add(entry.getId()); |
| 244 | + idsToBeDeleted.add(entry.getId()); |
229 | 245 | }
|
230 |
| - boolean anyRemoved = entries.removeIf(entry -> ids.contains(entry.getId())); |
231 |
| - if (anyRemoved) { |
232 |
| - toBeDeleted.forEach(entry -> entriesId.remove(entry.getId())); |
233 |
| - eventBus.post(new EntriesRemovedEvent(toBeDeleted, eventSource)); |
| 246 | + |
| 247 | + List<BibEntry> newEntries = new ArrayList<>(entries); |
| 248 | + newEntries.removeIf(entry -> idsToBeDeleted.contains(entry.getId())); |
| 249 | + |
| 250 | + toBeDeleted.forEach(entry -> { |
| 251 | + entriesId.remove(entry.getId()); |
| 252 | + removeEntryFromIndex(entry); |
| 253 | + }); |
| 254 | + |
| 255 | + entries.setAll(newEntries); |
| 256 | + eventBus.post(new EntriesRemovedEvent(toBeDeleted, eventSource)); |
| 257 | + } |
| 258 | + |
| 259 | + private void forEachCitationKey(BibEntry entry, Consumer<String> keyConsumer) { |
| 260 | + for (Field field : entry.getFields()) { |
| 261 | + if (field.getProperties().contains(FieldProperty.SINGLE_ENTRY_LINK) || field.getProperties().contains(FieldProperty.MULTIPLE_ENTRY_LINK)) { |
| 262 | + List<ParsedEntryLink> parsedLinks = entry.getEntryLinkList(field, this); |
| 263 | + |
| 264 | + for (ParsedEntryLink link : parsedLinks) { |
| 265 | + String key = link.getKey().trim(); |
| 266 | + if (!key.isEmpty()) { |
| 267 | + keyConsumer.accept(key); |
| 268 | + } |
| 269 | + } |
| 270 | + } |
234 | 271 | }
|
235 | 272 | }
|
236 | 273 |
|
| 274 | + public Set<BibEntry> getEntriesForCitationKey(String citationKey) { |
| 275 | + return citationIndex.getOrDefault(citationKey, Collections.emptySet()); |
| 276 | + } |
| 277 | + |
| 278 | + private Set<String> getReferencedCitationKeys(BibEntry entry) { |
| 279 | + Set<String> keys = new HashSet<>(); |
| 280 | + forEachCitationKey(entry, key -> keys.add(key)); |
| 281 | + return keys; |
| 282 | + } |
| 283 | + |
| 284 | + private void indexEntry(BibEntry entry) { |
| 285 | + forEachCitationKey(entry, key -> |
| 286 | + citationIndex.computeIfAbsent(key, k -> ConcurrentHashMap.newKeySet()).add(entry) |
| 287 | + ); |
| 288 | + } |
| 289 | + |
| 290 | + private void removeEntryFromIndex(BibEntry entry) { |
| 291 | + forEachCitationKey(entry, key -> { |
| 292 | + Set<BibEntry> entriesForKey = citationIndex.get(key); |
| 293 | + if (entriesForKey != null) { |
| 294 | + entriesForKey.remove(entry); |
| 295 | + if (entriesForKey.isEmpty()) { |
| 296 | + citationIndex.remove(key); |
| 297 | + } |
| 298 | + } |
| 299 | + }); |
| 300 | + } |
| 301 | + |
237 | 302 | /**
|
238 | 303 | * Returns the database's preamble.
|
239 | 304 | * If the preamble text consists only of whitespace, then also an empty optional is returned.
|
@@ -626,13 +691,11 @@ public String getNewLineSeparator() {
|
626 | 691 |
|
627 | 692 | /**
|
628 | 693 | * @return The index of the given entry in the list of entries, or -1 if the entry is not in the list.
|
629 |
| - * |
630 | 694 | * @implNote New entries are always added to the end of the list and always get a higher ID.
|
631 |
| - * See {@link org.jabref.model.entry.BibEntry#BibEntry(org.jabref.model.entry.types.EntryType) BibEntry}, |
632 |
| - * {@link org.jabref.model.entry.IdGenerator IdGenerator}, |
633 |
| - * {@link BibDatabase#insertEntries(List, EntriesEventSource) insertEntries}. |
634 |
| - * Therefore, using binary search to find the index. |
635 |
| - * |
| 695 | + * See {@link org.jabref.model.entry.BibEntry#BibEntry(org.jabref.model.entry.types.EntryType) BibEntry}, |
| 696 | + * {@link org.jabref.model.entry.IdGenerator IdGenerator}, |
| 697 | + * {@link BibDatabase#insertEntries(List, EntriesEventSource) insertEntries}. |
| 698 | + * Therefore, using binary search to find the index. |
636 | 699 | * @implNote IDs are zero-padded strings, so there is no need to convert them to integers for comparison.
|
637 | 700 | */
|
638 | 701 | public int indexOf(BibEntry bibEntry) {
|
|
0 commit comments