package org.jabref.gui.maintable; import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; import org.jabref.gui.groups.GroupMatcher; import org.jabref.gui.search.HitOrMissComparator; import org.jabref.gui.search.matchers.EverythingMatcher; import org.jabref.gui.search.matchers.SearchMatcher; import org.jabref.gui.util.comparator.IsMarkedComparator; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import ca.odell.glazedlists.BasicEventList; import ca.odell.glazedlists.EventList; import ca.odell.glazedlists.FilterList; import ca.odell.glazedlists.SortedList; import ca.odell.glazedlists.matchers.Matcher; public class MainTableDataModel { private final ListSynchronizer listSynchronizer; private final SortedList<BibEntry> sortedForUserDefinedTableColumnSorting; private final SortedList<BibEntry> sortedForMarkingSearchGrouping; private final StartStopListFilterAction filterSearchToggle; private final StartStopListFilterAction filterGroupToggle; private final EventList<BibEntry> finalList; private final FilterAndSortingState filterAndSortingState = new FilterAndSortingState(); public MainTableDataModel(BibDatabaseContext context) { List<BibEntry> entries = context.getDatabase().getEntries(); EventList<BibEntry> initialEventList = new BasicEventList<>(); initialEventList.addAll(entries); listSynchronizer = new ListSynchronizer(initialEventList); // This SortedList has a Comparator controlled by the TableComparatorChooser // we are going to install, which responds to user sorting selections: sortedForUserDefinedTableColumnSorting = new SortedList<>(initialEventList, null); // This SortedList applies afterwards, and floats marked entries: sortedForMarkingSearchGrouping = new SortedList<>(sortedForUserDefinedTableColumnSorting, null); FilterList<BibEntry> groupFilterList = new FilterList<>(sortedForMarkingSearchGrouping, EverythingMatcher.INSTANCE); filterGroupToggle = new StartStopListFilterAction(groupFilterList, GroupMatcher.INSTANCE, EverythingMatcher.INSTANCE); FilterList<BibEntry> searchFilterList = new FilterList<>(groupFilterList, EverythingMatcher.INSTANCE); filterSearchToggle = new StartStopListFilterAction(searchFilterList, SearchMatcher.INSTANCE, EverythingMatcher.INSTANCE); finalList = searchFilterList; } public void updateSortOrder() { Comparator<BibEntry> markingComparator = filterAndSortingState.markingState ? IsMarkedComparator.INSTANCE : null; Comparator<BibEntry> searchComparator = getSearchState() == DisplayOption.FLOAT ? new HitOrMissComparator(SearchMatcher.INSTANCE) : null; Comparator<BibEntry> groupingComparator = getGroupingState() == DisplayOption.FLOAT ? new HitOrMissComparator(GroupMatcher.INSTANCE) : null; GenericCompositeComparator comparator = new GenericCompositeComparator( markingComparator, searchComparator, groupingComparator ); sortedForMarkingSearchGrouping.getReadWriteLock().writeLock().lock(); try { if (sortedForMarkingSearchGrouping.getComparator() != comparator) { sortedForMarkingSearchGrouping.setComparator(comparator); } } finally { sortedForMarkingSearchGrouping.getReadWriteLock().writeLock().unlock(); } } public void updateSearchState(DisplayOption searchState) { Objects.requireNonNull(searchState); // fail fast if (filterAndSortingState.searchState == searchState) { return; } boolean updateSortOrder = false; if (filterAndSortingState.searchState == DisplayOption.FLOAT) { updateSortOrder = true; } else if (filterAndSortingState.searchState == DisplayOption.FILTER) { filterSearchToggle.stop(); } if (searchState == DisplayOption.FLOAT) { updateSortOrder = true; } else if (searchState == DisplayOption.FILTER) { filterSearchToggle.start(); } filterAndSortingState.searchState = searchState; if (updateSortOrder) { updateSortOrder(); } } public void updateGroupingState(DisplayOption groupingState) { Objects.requireNonNull(groupingState); // fail fast if (filterAndSortingState.groupingState == groupingState) { return; } boolean updateSortOrder = false; if (filterAndSortingState.groupingState == DisplayOption.FLOAT) { updateSortOrder = true; } else if (filterAndSortingState.groupingState == DisplayOption.FILTER) { filterGroupToggle.stop(); } if (groupingState == DisplayOption.FLOAT) { updateSortOrder = true; } else if (groupingState == DisplayOption.FILTER) { filterGroupToggle.start(); } filterAndSortingState.groupingState = groupingState; if (updateSortOrder) { updateSortOrder(); } } public DisplayOption getSearchState() { return filterAndSortingState.searchState; } DisplayOption getGroupingState() { return filterAndSortingState.groupingState; } public ListSynchronizer getListSynchronizer() { return this.listSynchronizer; } public void updateMarkingState(boolean floatMarkedEntries) { // fail fast if (filterAndSortingState.markingState == floatMarkedEntries) { return; } if (floatMarkedEntries) { filterAndSortingState.markingState = true; } else { filterAndSortingState.markingState = false; } updateSortOrder(); } EventList<BibEntry> getTableRows() { return finalList; } /** * Returns the List of entries sorted by a user-selected term. This is the * sorting before marking, search etc. applies. * <p> * Note: The returned List must not be modified from the outside * * @return The sorted list of entries. */ SortedList<BibEntry> getSortedForUserDefinedTableColumnSorting() { return sortedForUserDefinedTableColumnSorting; } public void updateGroupFilter() { if (getGroupingState() == DisplayOption.FILTER) { filterGroupToggle.start(); } else { filterGroupToggle.stop(); } } public enum DisplayOption { FLOAT, FILTER, DISABLED } static class FilterAndSortingState { // at the beginning, everything is disabled private DisplayOption searchState = DisplayOption.DISABLED; private DisplayOption groupingState = DisplayOption.DISABLED; private boolean markingState = false; } private static class GenericCompositeComparator implements Comparator<BibEntry> { private final List<Comparator<BibEntry>> comparators; @SafeVarargs public GenericCompositeComparator(Comparator<BibEntry>... comparators) { this.comparators = Arrays.asList(comparators).stream().filter(Objects::nonNull).collect(Collectors.toList()); } @Override public int compare(BibEntry lhs, BibEntry rhs) { for (Comparator<BibEntry> comp : comparators) { int result = comp.compare(lhs, rhs); if (result != 0) { return result; } } return 0; } } private static class StartStopListFilterAction { private final Matcher<BibEntry> active; private final Matcher<BibEntry> inactive; private final FilterList<BibEntry> list; private StartStopListFilterAction(FilterList<BibEntry> list, Matcher<BibEntry> active, Matcher<BibEntry> inactive) { this.list = list; this.active = active; this.inactive = inactive; list.setMatcher(inactive); } public void start() { update(active); } public void stop() { update(inactive); } private void update(Matcher<BibEntry> comparator) { list.getReadWriteLock().writeLock().lock(); try { list.setMatcher(comparator); } finally { list.getReadWriteLock().writeLock().unlock(); } } } }