source: josm/trunk/src/org/openstreetmap/josm/data/osm/FilterMatcher.java@ 13309

Last change on this file since 13309 was 13309, checked in by Don-vip, 6 years ago

fix #15766, see #15688 - fix performance regression introduced in r13229 when drawing a way of many nodes while the filter dialog is open

  • Property svn:eol-style set to native
File size: 12.6 KB
RevLine 
[3355]1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.osm;
3
4import java.util.ArrayList;
5import java.util.Collection;
6import java.util.List;
7
[12656]8import org.openstreetmap.josm.data.osm.search.SearchCompiler;
9import org.openstreetmap.josm.data.osm.search.SearchCompiler.Match;
10import org.openstreetmap.josm.data.osm.search.SearchCompiler.Not;
[12846]11import org.openstreetmap.josm.data.osm.search.SearchMode;
12import org.openstreetmap.josm.data.osm.search.SearchParseError;
[5442]13import org.openstreetmap.josm.tools.SubclassFilteredCollection;
[3355]14
[5423]15/**
16 * Class that encapsulates the filter logic, i.e. applies a list of
17 * filters to a primitive.
18 *
[5909]19 * Uses {@link Match#match} to see if the filter expression matches,
[5423]20 * cares for "inverted-flag" of the filters and combines the results of all active
21 * filters.
22 *
23 * There are two major use cases:
24 *
25 * (1) Hide features that you don't like to edit but get in the way, e.g.
26 * <code>landuse</code> or power lines. It is expected, that the inverted flag
27 * if false for these kind of filters.
28 *
29 * (2) Highlight certain features, that are currently interesting and hide everything
30 * else. This can be thought of as an improved search (Ctrl-F), where you can
31 * continue editing and don't loose the current selection. It is expected that
32 * the inverted flag of the filter is true in this case.
33 *
34 * In addition to the formal application of filter rules, some magic is applied
35 * to (hopefully) match the expectations of the user:
36 *
37 * (1) non-inverted: When hiding a way, all its untagged nodes are hidden as well.
38 * This avoids a "cloud of nodes", that normally isn't useful without the
39 * corresponding way.
40 *
41 * (2) inverted: When displaying a way, we show all its nodes, although the
42 * individual nodes do not match the filter expression. The reason is, that a
43 * way without its nodes cannot be edited properly.
44 *
[5442]45 * Multipolygons and (untagged) member ways are handled in a similar way.
[5423]46 */
[3355]47public class FilterMatcher {
48
[5437]49 /**
50 * Describes quality of the filtering.
51 *
52 * Depending on the context, this can either refer to disabled or
53 * to hidden primitives.
54 *
55 * The distinction is necessary, because untagged nodes should only
56 * "inherit" their filter property from the parent way, when the
57 * parent way is hidden (or disabled) "explicitly" (i.e. by a non-inverted
58 * filter). This way, filters like
59 * <code>["child type:way", inverted, Add]</code> show the
60 * untagged way nodes, as intended.
61 *
62 * This information is only needed for ways and relations, so nodes are
63 * either <code>NOT_FILTERED</code> or <code>PASSIV</code>.
64 */
65 public enum FilterType {
66 /** no filter applies */
67 NOT_FILTERED,
68 /** at least one non-inverted filter applies */
69 EXPLICIT,
70 /** at least one filter applies, but they are all inverted filters */
71 PASSIV
72 }
73
[3355]74 private static class FilterInfo {
[8285]75 private final Match match;
76 private final boolean isDelete;
77 private final boolean isInverted;
[3355]78
[12656]79 FilterInfo(Filter filter) throws SearchParseError {
[3355]80 if (filter.mode == SearchMode.remove || filter.mode == SearchMode.in_selection) {
81 isDelete = true;
82 } else {
83 isDelete = false;
84 }
85
[8811]86 Match compiled = SearchCompiler.compile(filter);
[8510]87 this.match = filter.inverted ? new Not(compiled) : compiled;
[3367]88 this.isInverted = filter.inverted;
[3355]89 }
90 }
91
[7005]92 private final List<FilterInfo> hiddenFilters = new ArrayList<>();
93 private final List<FilterInfo> disabledFilters = new ArrayList<>();
[3355]94
[9348]95 /**
96 * Clears the current filters, and adds the given filters
97 * @param filters the filters to add
[12656]98 * @throws SearchParseError if the search expression in one of the filters cannot be parsed
[9348]99 */
[12656]100 public void update(Collection<Filter> filters) throws SearchParseError {
[9348]101 reset();
102 for (Filter filter : filters) {
103 add(filter);
104 }
105 }
106
107 /**
108 * Clears the filters in use.
109 */
110 public void reset() {
[3355]111 hiddenFilters.clear();
112 disabledFilters.clear();
[9348]113 }
[3355]114
[9348]115 /**
116 * Adds a filter to the currently used filters
117 * @param filter the filter to add
[12656]118 * @throws SearchParseError if the search expression in the filter cannot be parsed
[9348]119 */
[12656]120 public void add(final Filter filter) throws SearchParseError {
[9348]121 if (!filter.enable) {
122 return;
123 }
[3355]124
[9348]125 FilterInfo fi = new FilterInfo(filter);
126 if (fi.isDelete) {
127 if (filter.hiding) {
128 // Remove only hide flag
129 hiddenFilters.add(fi);
[3367]130 } else {
[9348]131 // Remove both flags
[3367]132 disabledFilters.add(fi);
[9348]133 hiddenFilters.add(fi);
134 }
135 } else {
[11385]136 if (filter.mode == SearchMode.replace && filter.hiding) {
137 hiddenFilters.clear();
138 disabledFilters.clear();
[3355]139 }
[9348]140
141 disabledFilters.add(fi);
142 if (filter.hiding) {
143 hiddenFilters.add(fi);
144 }
[3367]145 }
146 }
[3355]147
[5437]148 /**
149 * Check if primitive is filtered.
150 * @param primitive the primitive to check
151 * @param hidden the minimum level required for the primitive to count as filtered
152 * @return when hidden is true, returns whether the primitive is hidden
153 * when hidden is false, returns whether the primitive is disabled or hidden
154 */
[8870]155 private static boolean isFiltered(OsmPrimitive primitive, boolean hidden) {
[5437]156 return hidden ? primitive.isDisabledAndHidden() : primitive.isDisabled();
[3367]157 }
158
[5437]159 /**
160 * Check if primitive is hidden explicitly.
161 * Only used for ways and relations.
162 * @param primitive the primitive to check
163 * @param hidden the level where the check is performed
164 * @return true, if at least one non-inverted filter applies to the primitive
165 */
[8870]166 private static boolean isFilterExplicit(OsmPrimitive primitive, boolean hidden) {
[5437]167 return hidden ? primitive.getHiddenType() : primitive.getDisabledType();
168 }
169
170 /**
171 * Check if all parent ways are filtered.
172 * @param primitive the primitive to check
173 * @param hidden parameter that indicates the minimum level of filtering:
174 * true when objects need to be hidden to count as filtered and
175 * false when it suffices to be disabled to count as filtered
176 * @return true if (a) there is at least one parent way
177 * (b) all parent ways are filtered at least at the level indicated by the
178 * parameter <code>hidden</code> and
179 * (c) at least one of the parent ways is explicitly filtered
180 */
[8870]181 private static boolean allParentWaysFiltered(OsmPrimitive primitive, boolean hidden) {
[3367]182 List<OsmPrimitive> refs = primitive.getReferrers();
[5437]183 boolean isExplicit = false;
[3367]184 for (OsmPrimitive p: refs) {
[3379]185 if (p instanceof Way) {
[5437]186 if (!isFiltered(p, hidden))
[3379]187 return false;
[5437]188 isExplicit |= isFilterExplicit(p, hidden);
[3379]189 }
[3355]190 }
[5437]191 return isExplicit;
[3355]192 }
193
[8870]194 private static boolean oneParentWayNotFiltered(OsmPrimitive primitive, boolean hidden) {
[3367]195 List<OsmPrimitive> refs = primitive.getReferrers();
196 for (OsmPrimitive p: refs) {
[5437]197 if (p instanceof Way && !isFiltered(p, hidden))
[3367]198 return true;
199 }
200
201 return false;
202 }
203
[8870]204 private static boolean allParentMultipolygonsFiltered(OsmPrimitive primitive, boolean hidden) {
[5442]205 boolean isExplicit = false;
206 for (Relation r : new SubclassFilteredCollection<OsmPrimitive, Relation>(
[10716]207 primitive.getReferrers(), OsmPrimitive::isMultipolygon)) {
[5442]208 if (!isFiltered(r, hidden))
209 return false;
210 isExplicit |= isFilterExplicit(r, hidden);
211 }
212 return isExplicit;
213 }
214
[8870]215 private static boolean oneParentMultipolygonNotFiltered(OsmPrimitive primitive, boolean hidden) {
[5442]216 for (Relation r : new SubclassFilteredCollection<OsmPrimitive, Relation>(
[10716]217 primitive.getReferrers(), OsmPrimitive::isMultipolygon)) {
[5442]218 if (!isFiltered(r, hidden))
219 return true;
220 }
221 return false;
222 }
223
[8870]224 private static FilterType test(List<FilterInfo> filters, OsmPrimitive primitive, boolean hidden) {
[13309]225 if (primitive.isIncomplete() || primitive.isPreserved())
[5437]226 return FilterType.NOT_FILTERED;
[3401]227
[5437]228 boolean filtered = false;
[5423]229 // If the primitive is "explicitly" hidden by a non-inverted filter.
230 // Only interesting for nodes.
[5437]231 boolean explicitlyFiltered = false;
[3367]232
[3355]233 for (FilterInfo fi: filters) {
[5423]234 if (fi.isDelete) {
[5437]235 if (filtered && fi.match.match(primitive)) {
236 filtered = false;
[5423]237 }
238 } else {
[5437]239 if ((!filtered || (!explicitlyFiltered && !fi.isInverted)) && fi.match.match(primitive)) {
240 filtered = true;
[5423]241 if (!fi.isInverted) {
[5437]242 explicitlyFiltered = true;
[5423]243 }
244 }
[3355]245 }
246 }
[3367]247
248 if (primitive instanceof Node) {
[5442]249 if (filtered) {
250 // If there is a parent way, that is not hidden, we show the
251 // node anyway, unless there is no non-inverted filter that
252 // applies to the node directly.
253 if (explicitlyFiltered)
254 return FilterType.PASSIV;
255 else {
256 if (oneParentWayNotFiltered(primitive, hidden))
257 return FilterType.NOT_FILTERED;
258 else
259 return FilterType.PASSIV;
260 }
261 } else {
[5437]262 if (!primitive.isTagged() && allParentWaysFiltered(primitive, hidden))
[5442]263 // Technically not hidden by any filter, but we hide it anyway, if
264 // it is untagged and all parent ways are hidden.
[5437]265 return FilterType.PASSIV;
266 else
267 return FilterType.NOT_FILTERED;
268 }
[5442]269 } else if (primitive instanceof Way) {
270 if (filtered) {
271 if (explicitlyFiltered)
272 return FilterType.EXPLICIT;
273 else {
274 if (oneParentMultipolygonNotFiltered(primitive, hidden))
275 return FilterType.NOT_FILTERED;
276 else
277 return FilterType.PASSIV;
278 }
279 } else {
280 if (!primitive.isTagged() && allParentMultipolygonsFiltered(primitive, hidden))
281 return FilterType.EXPLICIT;
[5437]282 else
283 return FilterType.NOT_FILTERED;
284 }
285 } else {
286 if (filtered)
287 return explicitlyFiltered ? FilterType.EXPLICIT : FilterType.PASSIV;
288 else
289 return FilterType.NOT_FILTERED;
290 }
[3367]291
[3355]292 }
293
[5437]294 /**
295 * Check if primitive is hidden.
296 * The filter flags for all parent objects must be set correctly, when
297 * calling this method.
298 * @param primitive the primitive
299 * @return FilterType.NOT_FILTERED when primitive is not hidden;
300 * FilterType.EXPLICIT when primitive is hidden and there is a non-inverted
301 * filter that applies;
302 * FilterType.PASSIV when primitive is hidden and all filters that apply
303 * are inverted
304 */
305 public FilterType isHidden(OsmPrimitive primitive) {
[3367]306 return test(hiddenFilters, primitive, true);
[3355]307 }
308
[5437]309 /**
310 * Check if primitive is disabled.
311 * The filter flags for all parent objects must be set correctly, when
312 * calling this method.
313 * @param primitive the primitive
314 * @return FilterType.NOT_FILTERED when primitive is not disabled;
315 * FilterType.EXPLICIT when primitive is disabled and there is a non-inverted
316 * filter that applies;
317 * FilterType.PASSIV when primitive is disabled and all filters that apply
318 * are inverted
319 */
320 public FilterType isDisabled(OsmPrimitive primitive) {
[3367]321 return test(disabledFilters, primitive, false);
[3355]322 }
323
[12383]324 /**
325 * Returns a new {@code FilterMatcher} containing the given filters.
326 * @param filters filters to add to the resulting filter matcher
327 * @return a new {@code FilterMatcher} containing the given filters
[12656]328 * @throws SearchParseError if the search expression in a filter cannot be parsed
[12383]329 * @since 12383
330 */
[12656]331 public static FilterMatcher of(Filter... filters) throws SearchParseError {
[12383]332 FilterMatcher result = new FilterMatcher();
333 for (Filter filter : filters) {
334 result.add(filter);
335 }
336 return result;
337 }
[3355]338}
Note: See TracBrowser for help on using the repository browser.