Skip to content

Commit 5c3d6e2

Browse files
committed
searchfilters: a framework to create sort and content filter objects
FilterContainer.java: ===================== This class is a container that keeps either content filters or sort filters organized. Sort/content filters ({@link FilterItem}s) are organized within {@link FilterGroup}s. FilterGroup.java: ================= This class represents a filter category/group. For example 'Sort order'. Its main purpose is to host a bunch of {@link FilterItem}s that belong to that group. Eg. 'Relevance', 'Views', 'Rating' FilterItem.java: ================ This class represents a single filter option. *More in detail:* For example youtube offers the filter group 'Sort order'. This group consists of filter options like 'Relevance', 'Views', 'Rating' etc. -> for each filter option a FilterItem has to be created. BaseSearchFilters.java: ======================= The base class for every service describing their {@link FilterItem}s, {@link FilterGroup}s, the relation between content filters and sort filters.
1 parent eb07d70 commit 5c3d6e2

4 files changed

Lines changed: 452 additions & 0 deletions

File tree

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
// Created by evermind-zz 2022, licensed GNU GPL version 3 or later
2+
3+
package org.schabi.newpipe.extractor.search.filter;
4+
5+
import org.schabi.newpipe.extractor.services.youtube.search.filter.YoutubeFilters;
6+
7+
import java.util.HashMap;
8+
import java.util.LinkedList;
9+
import java.util.List;
10+
import java.util.Map;
11+
12+
/**
13+
* The base class for every service describing their {@link FilterItem}s,
14+
* {@link FilterGroup}s, the relation between content filters and sort filters.
15+
*/
16+
public abstract class BaseSearchFilters {
17+
18+
protected final Map<Integer, FilterContainer> sortFilterVariants = new HashMap<>();
19+
protected FilterGroup.Factory groupsFactory = new FilterGroup.Factory();
20+
protected List<FilterItem> selectedContentFilter = null;
21+
protected List<FilterItem> selectedSortFilter;
22+
protected FilterContainer contentFiltersVariant;
23+
protected List<FilterGroup> contentFilterGroups = new LinkedList<>();
24+
25+
protected BaseSearchFilters() {
26+
init();
27+
build();
28+
}
29+
30+
/**
31+
* Set the user selected sort filters which the user has selected in the UI.
32+
*
33+
* @param selectedSortFilter list with sort filters identifiers
34+
*/
35+
public void setSelectedSortFilter(final List<FilterItem> selectedSortFilter) {
36+
this.selectedSortFilter = selectedSortFilter;
37+
}
38+
39+
/**
40+
* Set the selected content filter
41+
*
42+
* @param selectedContentFilter the name of the content filter
43+
*/
44+
public void setSelectedContentFilter(final List<FilterItem> selectedContentFilter) {
45+
this.selectedContentFilter = selectedContentFilter;
46+
}
47+
48+
/**
49+
* Evaluate content and sort filters. This method should be run after:
50+
* {@link #setSelectedContentFilter(List)} and {@link #setSelectedSortFilter(List)}
51+
* <p>
52+
* Note: Whether you should implement this method or {@link #evaluateSelectedContentFilters()}
53+
* and/or {@link #evaluateSelectedSortFilters()} depends on your service needs and/or
54+
* how you want to implement.
55+
*
56+
* @return the query that should be appended to the searchUrl/whatever
57+
*/
58+
public String evaluateSelectedFilters(final String searchString) {
59+
// please implement method in derived class if you want to use it
60+
return null;
61+
}
62+
63+
/**
64+
* Evaluate content filters. This method should be run after:
65+
* {@link #setSelectedContentFilter(List)}
66+
*
67+
* @return the sortQuery that should be appended to the searchUrl/whatever
68+
*/
69+
public String evaluateSelectedContentFilters() {
70+
// please implement method in derived class if you want to use it
71+
return null;
72+
}
73+
74+
/**
75+
* Evaluate sort filters. This method should be run after:
76+
* {@link #setSelectedSortFilter(List)}
77+
*
78+
* @return the contentQuery that should be appended to the searchUrl/whatever
79+
*/
80+
public String evaluateSelectedSortFilters() {
81+
// please implement method in derived class if you want to use it
82+
return null;
83+
}
84+
85+
/**
86+
* create all 'sort' and 'content filter' items and all 'sort filter variants' in this method.
87+
* See eg. {@link YoutubeFilters#init()}
88+
*/
89+
protected abstract void init();
90+
91+
/**
92+
* Transform the filter group list into an array and create the {@link FilterContainer}
93+
* with the content filters that are present for this service (eg. YouTube).
94+
*/
95+
protected void build() {
96+
if (contentFilterGroups == null) {
97+
throw new RuntimeException("Never call method build() twice");
98+
}
99+
100+
this.contentFiltersVariant = new FilterContainer(
101+
contentFilterGroups.toArray(new FilterGroup[0]));
102+
103+
// building done
104+
contentFilterGroups.clear();
105+
contentFilterGroups = null;
106+
}
107+
108+
/**
109+
* Add content Filter SortVariants.
110+
* <p>
111+
* Each content filter may have a corresponding sort filter variant.
112+
*
113+
* @param contentFilterId the content filter this sort variant applies to
114+
* @param variant the corresponding sort filter variant
115+
*/
116+
protected void addContentFilterSortVariant(
117+
final int contentFilterId,
118+
final FilterContainer variant) {
119+
this.sortFilterVariants.put(contentFilterId, variant);
120+
}
121+
122+
/**
123+
* Get (if available) the sort filter variant for this content filter id.
124+
*
125+
* @param identifier the id of a content {@link FilterItem}
126+
* @return the sort filter variant for above stated content filter item. Null if there is none.
127+
*/
128+
public FilterContainer getContentFilterSortFilterVariant(
129+
final int identifier) {
130+
return this.sortFilterVariants.get(identifier);
131+
}
132+
133+
/**
134+
* Get all available content filters for this service.
135+
*
136+
* @return all available content filters
137+
*/
138+
public FilterContainer getContentFilters() {
139+
return this.contentFiltersVariant;
140+
}
141+
142+
/**
143+
* Get the {@link FilterItem} for the corresponding Id.
144+
*
145+
* @param filterId the filter id
146+
* @return the corresponding filter, null if none exists
147+
*/
148+
public FilterItem getFilterItem(final int filterId) {
149+
return groupsFactory.getFilterForId(filterId);
150+
}
151+
152+
/**
153+
* Add the content filter groups that should be available
154+
*/
155+
protected void addContentFilterGroup(final FilterGroup filterGroup) {
156+
if (contentFilterGroups != null) {
157+
contentFilterGroups.add(filterGroup);
158+
} else {
159+
throw new RuntimeException("Never call this method after build()");
160+
}
161+
}
162+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Created by evermind-zz 2022, licensed GNU GPL version 3 or later
2+
3+
package org.schabi.newpipe.extractor.search.filter;
4+
5+
import java.util.HashMap;
6+
import java.util.Map;
7+
8+
/**
9+
* This class is a container that keeps either content filters or sort filters organized.
10+
*
11+
* Sort/content filters ({@link FilterItem}s) are organized within {@link FilterGroup}s.
12+
*/
13+
public final class FilterContainer {
14+
15+
/**
16+
* Mark {@link FilterItem}'s and {@link FilterGroup}'s which identifier is not (yet) set.
17+
*/
18+
public static final int ITEM_IDENTIFIER_UNKNOWN = -1;
19+
20+
private final Map<Integer, FilterItem> idToFilterItem = new HashMap<>();
21+
private final FilterGroup[] filterGroups;
22+
23+
public FilterContainer(final FilterGroup[] filterGroups) {
24+
this.filterGroups = filterGroups;
25+
for (final FilterGroup group : filterGroups) {
26+
for (final FilterItem item : group.getFilterItems()) {
27+
idToFilterItem.put(item.getIdentifier(), item);
28+
}
29+
}
30+
}
31+
32+
/**
33+
* Quickly access a {@link FilterItem} that belongs to this {@link FilterContainer}.
34+
*
35+
* @param id the identifier of the {@link FilterItem}
36+
* @return
37+
*/
38+
public FilterItem getFilterItem(final int id) {
39+
return idToFilterItem.get(id);
40+
}
41+
42+
public FilterGroup[] getFilterGroups() {
43+
return filterGroups;
44+
}
45+
}
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
// Created by evermind-zz 2022, licensed GNU GPL version 3 or later
2+
3+
package org.schabi.newpipe.extractor.search.filter;
4+
5+
import java.util.HashMap;
6+
import java.util.Map;
7+
8+
/**
9+
* This class represents a filter category/group. For example 'Sort order'.
10+
* <p>
11+
* Its main purpose is to host a bunch of {@link FilterItem}s that belong to that
12+
* group. Eg. 'Relevance', 'Views', 'Rating'
13+
*/
14+
public final class FilterGroup {
15+
16+
/**
17+
* {@link #getIdentifier()}
18+
*/
19+
private final int identifier;
20+
21+
/**
22+
* The name of the filter group that the user will see
23+
*/
24+
private final String groupName;
25+
26+
/**
27+
* Specify whether only one item can be selected in this group at a time.
28+
*/
29+
private final boolean onlyOneCheckable;
30+
31+
/**
32+
* Each group may have a default value that should be selected.
33+
* <p>
34+
* It should be set to the the {@link FilterItem}'s id. If there is no default option
35+
* it should be set to {@link FilterContainer#ITEM_IDENTIFIER_UNKNOWN}
36+
*/
37+
private final int defaultSelectedFilterId;
38+
39+
/**
40+
* The filter items that belong to this {@link FilterGroup}.
41+
*/
42+
private final FilterItem[] filterItems;
43+
44+
/**
45+
* {@link #getAllSortFilters()}.
46+
*/
47+
private final FilterContainer allSortFilters;
48+
49+
private FilterGroup(final int identifier,
50+
final String groupName,
51+
final boolean onlyOneCheckable,
52+
final int defaultSelectedFilterId,
53+
final FilterItem[] filterItems,
54+
final FilterContainer allSortFilters) {
55+
this.identifier = identifier;
56+
this.groupName = groupName;
57+
this.onlyOneCheckable = onlyOneCheckable;
58+
this.defaultSelectedFilterId = defaultSelectedFilterId;
59+
this.filterItems = filterItems;
60+
this.allSortFilters = allSortFilters;
61+
}
62+
63+
64+
/**
65+
* If this group is a content filter and has corresponding sort filters, this
66+
* {@link FilterContainer} contains all available sort filters for this group.
67+
*
68+
* @return may be null as not all {@link FilterGroup}s have sort filters.
69+
*/
70+
public FilterContainer getAllSortFilters() {
71+
return allSortFilters;
72+
}
73+
74+
/**
75+
* {@link FilterItem#getIdentifier()}
76+
*/
77+
public int getIdentifier() {
78+
return this.identifier;
79+
}
80+
81+
/**
82+
* {@link #groupName}
83+
*/
84+
public String getName() {
85+
return groupName;
86+
}
87+
88+
/**
89+
* {@link #defaultSelectedFilterId}
90+
*/
91+
public int getDefaultSelectedFilterId() {
92+
return defaultSelectedFilterId;
93+
}
94+
95+
/**
96+
* {@link #filterItems}
97+
*/
98+
public FilterItem[] getFilterItems() {
99+
return filterItems;
100+
}
101+
102+
/**
103+
* {@link #onlyOneCheckable}
104+
*/
105+
public boolean isOnlyOneCheckable() {
106+
return onlyOneCheckable;
107+
}
108+
109+
/**
110+
* Factory for building {@link FilterGroup}s.
111+
* <p>
112+
* Each service should only have one instance.
113+
* This is implemented in {@link BaseSearchFilters}
114+
*/
115+
public static class Factory {
116+
117+
/**
118+
* A map that has all {@link FilterItem}s that are relevant for one service. Eg. Youtube
119+
*/
120+
public final Map<Integer, FilterItem> filtersMap = new HashMap<>();
121+
122+
/**
123+
* Check if a {@link FilterItem} has a unique id.
124+
*
125+
* @param filterItems a map with the previously added {@link FilterItem}'s to compare with.
126+
* @param item the new {@link FilterItem} that should be added.
127+
*/
128+
void uniqueIdChecker(final Map<Integer, FilterItem> filterItems,
129+
final FilterItem item) {
130+
131+
if (item.getIdentifier() == FilterContainer.ITEM_IDENTIFIER_UNKNOWN
132+
&& !(item instanceof FilterItem.DividerItem)) {
133+
throw new InvalidFilterIdException("Filter ID "
134+
+ item.getIdentifier() + " aka FilterContainer.ITEM_IDENTIFIER_UNKNOWN"
135+
+ " for \"" + item.getName() + "\" not allowed");
136+
}
137+
138+
if (filterItems.containsKey(item.getIdentifier())) {
139+
final FilterItem storedItem = filterItems.get(item.getIdentifier());
140+
throw new InvalidFilterIdException("Filter ID "
141+
+ item.getIdentifier() + " for \"" + item.getName()
142+
+ "\" already taken from \"" + storedItem.getName() + "\"");
143+
}
144+
}
145+
146+
/**
147+
* Add a new {@link FilterItem} that is relevant to this service.
148+
* <p>
149+
* The {@link FilterItem}s are accessible by their id via {@link #getFilterForId(int)}
150+
*
151+
* @param filter the new {@link FilterItem} to be added to the factory.
152+
* @return the identifier of the {@link FilterItem}
153+
*/
154+
public int addFilterItem(final FilterItem filter) {
155+
uniqueIdChecker(filtersMap, filter);
156+
filtersMap.put(filter.getIdentifier(), filter);
157+
return filter.getIdentifier();
158+
}
159+
160+
public FilterGroup createFilterGroup(final int identifier,
161+
final String groupName,
162+
final boolean onlyOneCheckable,
163+
final int defaultSelectedFilterId,
164+
final FilterItem[] filterItems,
165+
final FilterContainer allSortFilters) {
166+
return new FilterGroup(identifier, groupName, onlyOneCheckable,
167+
defaultSelectedFilterId, filterItems, allSortFilters);
168+
}
169+
170+
/**
171+
* Get previously via {@link #addFilterItem(FilterItem)} added {@link FilterItem}.
172+
*
173+
* @param identifier the id of the desired {@link FilterItem}
174+
* @return the desired {@link FilterItem}
175+
*/
176+
public FilterItem getFilterForId(final int identifier) {
177+
return filtersMap.get(identifier);
178+
}
179+
180+
private static class InvalidFilterIdException extends RuntimeException {
181+
InvalidFilterIdException(final String message) {
182+
super(message);
183+
}
184+
}
185+
}
186+
}

0 commit comments

Comments
 (0)