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

Last change on this file since 12537 was 12383, checked in by Don-vip, 7 years ago

see #14929 - new methods to ease the direct handling of filters

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