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

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

see #5144 - fix NPE

  • Property svn:eol-style set to native
File size: 12.1 KB
Line 
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
8import org.openstreetmap.josm.Main;
9import org.openstreetmap.josm.actions.search.SearchAction.SearchMode;
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;
14import org.openstreetmap.josm.tools.SubclassFilteredCollection;
15
16/**
17 * Class that encapsulates the filter logic, i.e. applies a list of
18 * filters to a primitive.
19 *
20 * Uses {@link Match#match} to see if the filter expression matches,
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 *
46 * Multipolygons and (untagged) member ways are handled in a similar way.
47 */
48public class FilterMatcher {
49
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
75 private static class FilterInfo {
76 private final Match match;
77 private final boolean isDelete;
78 private final boolean isInverted;
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
87 Match compiled = SearchCompiler.compile(filter);
88 this.match = filter.inverted ? new Not(compiled) : compiled;
89 this.isInverted = filter.inverted;
90 }
91 }
92
93 private final List<FilterInfo> hiddenFilters = new ArrayList<>();
94 private final List<FilterInfo> disabledFilters = new ArrayList<>();
95
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 */
101 public void update(Collection<Filter> filters) throws ParseError {
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() {
112 hiddenFilters.clear();
113 disabledFilters.clear();
114 }
115
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 }
125
126 FilterInfo fi = new FilterInfo(filter);
127 if (fi.isDelete) {
128 if (filter.hiding) {
129 // Remove only hide flag
130 hiddenFilters.add(fi);
131 } else {
132 // Remove both flags
133 disabledFilters.add(fi);
134 hiddenFilters.add(fi);
135 }
136 } else {
137 if (filter.mode == SearchMode.replace && filter.hiding) {
138 hiddenFilters.clear();
139 disabledFilters.clear();
140 }
141
142 disabledFilters.add(fi);
143 if (filter.hiding) {
144 hiddenFilters.add(fi);
145 }
146 }
147 }
148
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 */
156 private static boolean isFiltered(OsmPrimitive primitive, boolean hidden) {
157 return hidden ? primitive.isDisabledAndHidden() : primitive.isDisabled();
158 }
159
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 */
167 private static boolean isFilterExplicit(OsmPrimitive primitive, boolean hidden) {
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 */
182 private static boolean allParentWaysFiltered(OsmPrimitive primitive, boolean hidden) {
183 List<OsmPrimitive> refs = primitive.getReferrers();
184 boolean isExplicit = false;
185 for (OsmPrimitive p: refs) {
186 if (p instanceof Way) {
187 if (!isFiltered(p, hidden))
188 return false;
189 isExplicit |= isFilterExplicit(p, hidden);
190 }
191 }
192 return isExplicit;
193 }
194
195 private static boolean oneParentWayNotFiltered(OsmPrimitive primitive, boolean hidden) {
196 List<OsmPrimitive> refs = primitive.getReferrers();
197 for (OsmPrimitive p: refs) {
198 if (p instanceof Way && !isFiltered(p, hidden))
199 return true;
200 }
201
202 return false;
203 }
204
205 private static boolean allParentMultipolygonsFiltered(OsmPrimitive primitive, boolean hidden) {
206 boolean isExplicit = false;
207 for (Relation r : new SubclassFilteredCollection<OsmPrimitive, Relation>(
208 primitive.getReferrers(), OsmPrimitive::isMultipolygon)) {
209 if (!isFiltered(r, hidden))
210 return false;
211 isExplicit |= isFilterExplicit(r, hidden);
212 }
213 return isExplicit;
214 }
215
216 private static boolean oneParentMultipolygonNotFiltered(OsmPrimitive primitive, boolean hidden) {
217 for (Relation r : new SubclassFilteredCollection<OsmPrimitive, Relation>(
218 primitive.getReferrers(), OsmPrimitive::isMultipolygon)) {
219 if (!isFiltered(r, hidden))
220 return true;
221 }
222 return false;
223 }
224
225 private static FilterType test(List<FilterInfo> filters, OsmPrimitive primitive, boolean hidden) {
226
227 if (primitive.isIncomplete() || (Main.map != null && Main.map.mapMode.getPreservedPrimitives().contains(primitive)))
228 return FilterType.NOT_FILTERED;
229
230 boolean filtered = false;
231 // If the primitive is "explicitly" hidden by a non-inverted filter.
232 // Only interesting for nodes.
233 boolean explicitlyFiltered = false;
234
235 for (FilterInfo fi: filters) {
236 if (fi.isDelete) {
237 if (filtered && fi.match.match(primitive)) {
238 filtered = false;
239 }
240 } else {
241 if ((!filtered || (!explicitlyFiltered && !fi.isInverted)) && fi.match.match(primitive)) {
242 filtered = true;
243 if (!fi.isInverted) {
244 explicitlyFiltered = true;
245 }
246 }
247 }
248 }
249
250 if (primitive instanceof Node) {
251 if (filtered) {
252 // If there is a parent way, that is not hidden, we show the
253 // node anyway, unless there is no non-inverted filter that
254 // applies to the node directly.
255 if (explicitlyFiltered)
256 return FilterType.PASSIV;
257 else {
258 if (oneParentWayNotFiltered(primitive, hidden))
259 return FilterType.NOT_FILTERED;
260 else
261 return FilterType.PASSIV;
262 }
263 } else {
264 if (!primitive.isTagged() && allParentWaysFiltered(primitive, hidden))
265 // Technically not hidden by any filter, but we hide it anyway, if
266 // it is untagged and all parent ways are hidden.
267 return FilterType.PASSIV;
268 else
269 return FilterType.NOT_FILTERED;
270 }
271 } else if (primitive instanceof Way) {
272 if (filtered) {
273 if (explicitlyFiltered)
274 return FilterType.EXPLICIT;
275 else {
276 if (oneParentMultipolygonNotFiltered(primitive, hidden))
277 return FilterType.NOT_FILTERED;
278 else
279 return FilterType.PASSIV;
280 }
281 } else {
282 if (!primitive.isTagged() && allParentMultipolygonsFiltered(primitive, hidden))
283 return FilterType.EXPLICIT;
284 else
285 return FilterType.NOT_FILTERED;
286 }
287 } else {
288 if (filtered)
289 return explicitlyFiltered ? FilterType.EXPLICIT : FilterType.PASSIV;
290 else
291 return FilterType.NOT_FILTERED;
292 }
293
294 }
295
296 /**
297 * Check if primitive is hidden.
298 * The filter flags for all parent objects must be set correctly, when
299 * calling this method.
300 * @param primitive the primitive
301 * @return FilterType.NOT_FILTERED when primitive is not hidden;
302 * FilterType.EXPLICIT when primitive is hidden and there is a non-inverted
303 * filter that applies;
304 * FilterType.PASSIV when primitive is hidden and all filters that apply
305 * are inverted
306 */
307 public FilterType isHidden(OsmPrimitive primitive) {
308 return test(hiddenFilters, primitive, true);
309 }
310
311 /**
312 * Check if primitive is disabled.
313 * The filter flags for all parent objects must be set correctly, when
314 * calling this method.
315 * @param primitive the primitive
316 * @return FilterType.NOT_FILTERED when primitive is not disabled;
317 * FilterType.EXPLICIT when primitive is disabled and there is a non-inverted
318 * filter that applies;
319 * FilterType.PASSIV when primitive is disabled and all filters that apply
320 * are inverted
321 */
322 public FilterType isDisabled(OsmPrimitive primitive) {
323 return test(disabledFilters, primitive, false);
324 }
325
326}
Note: See TracBrowser for help on using the repository browser.