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

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

sonar - squid:S2325 - "private" methods that don't access instance data should be "static"

  • Property svn:eol-style set to native
File size: 11.6 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.actions.search.SearchAction.SearchMode;
9import org.openstreetmap.josm.actions.search.SearchCompiler;
10import org.openstreetmap.josm.actions.search.SearchCompiler.Match;
11import org.openstreetmap.josm.actions.search.SearchCompiler.Not;
12import org.openstreetmap.josm.actions.search.SearchCompiler.ParseError;
13import org.openstreetmap.josm.tools.SubclassFilteredCollection;
14
15/**
16 * Class that encapsulates the filter logic, i.e. applies a list of
17 * filters to a primitive.
18 *
19 * Uses {@link Match#match} to see if the filter expression matches,
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 *
45 * Multipolygons and (untagged) member ways are handled in a similar way.
46 */
47public class FilterMatcher {
48
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
74 private static class FilterInfo {
75 private final Match match;
76 private final boolean isDelete;
77 private final boolean isInverted;
78
79 FilterInfo(Filter filter) throws ParseError {
80 if (filter.mode == SearchMode.remove || filter.mode == SearchMode.in_selection) {
81 isDelete = true;
82 } else {
83 isDelete = false;
84 }
85
86 Match compiled = SearchCompiler.compile(filter);
87 this.match = filter.inverted ? new Not(compiled) : compiled;
88 this.isInverted = filter.inverted;
89 }
90 }
91
92 private final List<FilterInfo> hiddenFilters = new ArrayList<>();
93 private final List<FilterInfo> disabledFilters = new ArrayList<>();
94
95 public void update(Collection<Filter> filters) throws ParseError {
96 hiddenFilters.clear();
97 disabledFilters.clear();
98
99 for (Filter filter: filters) {
100
101 if (!filter.enable) {
102 continue;
103 }
104
105 FilterInfo fi = new FilterInfo(filter);
106 if (fi.isDelete) {
107 if (filter.hiding) {
108 // Remove only hide flag
109 hiddenFilters.add(fi);
110 } else {
111 // Remove both flags
112 disabledFilters.add(fi);
113 hiddenFilters.add(fi);
114 }
115 } else {
116 if (filter.mode == SearchMode.replace) {
117 if (filter.hiding) {
118 hiddenFilters.clear();
119 disabledFilters.clear();
120 }
121 }
122
123 disabledFilters.add(fi);
124 if (filter.hiding) {
125 hiddenFilters.add(fi);
126 }
127 }
128 }
129 }
130
131 /**
132 * Check if primitive is filtered.
133 * @param primitive the primitive to check
134 * @param hidden the minimum level required for the primitive to count as filtered
135 * @return when hidden is true, returns whether the primitive is hidden
136 * when hidden is false, returns whether the primitive is disabled or hidden
137 */
138 private static boolean isFiltered(OsmPrimitive primitive, boolean hidden) {
139 return hidden ? primitive.isDisabledAndHidden() : primitive.isDisabled();
140 }
141
142 /**
143 * Check if primitive is hidden explicitly.
144 * Only used for ways and relations.
145 * @param primitive the primitive to check
146 * @param hidden the level where the check is performed
147 * @return true, if at least one non-inverted filter applies to the primitive
148 */
149 private static boolean isFilterExplicit(OsmPrimitive primitive, boolean hidden) {
150 return hidden ? primitive.getHiddenType() : primitive.getDisabledType();
151 }
152
153 /**
154 * Check if all parent ways are filtered.
155 * @param primitive the primitive to check
156 * @param hidden parameter that indicates the minimum level of filtering:
157 * true when objects need to be hidden to count as filtered and
158 * false when it suffices to be disabled to count as filtered
159 * @return true if (a) there is at least one parent way
160 * (b) all parent ways are filtered at least at the level indicated by the
161 * parameter <code>hidden</code> and
162 * (c) at least one of the parent ways is explicitly filtered
163 */
164 private static boolean allParentWaysFiltered(OsmPrimitive primitive, boolean hidden) {
165 List<OsmPrimitive> refs = primitive.getReferrers();
166 boolean isExplicit = false;
167 for (OsmPrimitive p: refs) {
168 if (p instanceof Way) {
169 if (!isFiltered(p, hidden))
170 return false;
171 isExplicit |= isFilterExplicit(p, hidden);
172 }
173 }
174 return isExplicit;
175 }
176
177 private static boolean oneParentWayNotFiltered(OsmPrimitive primitive, boolean hidden) {
178 List<OsmPrimitive> refs = primitive.getReferrers();
179 for (OsmPrimitive p: refs) {
180 if (p instanceof Way && !isFiltered(p, hidden))
181 return true;
182 }
183
184 return false;
185 }
186
187 private static boolean allParentMultipolygonsFiltered(OsmPrimitive primitive, boolean hidden) {
188 boolean isExplicit = false;
189 for (Relation r : new SubclassFilteredCollection<OsmPrimitive, Relation>(
190 primitive.getReferrers(), OsmPrimitive.multipolygonPredicate)) {
191 if (!isFiltered(r, hidden))
192 return false;
193 isExplicit |= isFilterExplicit(r, hidden);
194 }
195 return isExplicit;
196 }
197
198 private static boolean oneParentMultipolygonNotFiltered(OsmPrimitive primitive, boolean hidden) {
199 for (Relation r : new SubclassFilteredCollection<OsmPrimitive, Relation>(
200 primitive.getReferrers(), OsmPrimitive.multipolygonPredicate)) {
201 if (!isFiltered(r, hidden))
202 return true;
203 }
204 return false;
205 }
206
207 private static FilterType test(List<FilterInfo> filters, OsmPrimitive primitive, boolean hidden) {
208
209 if (primitive.isIncomplete())
210 return FilterType.NOT_FILTERED;
211
212 boolean filtered = false;
213 // If the primitive is "explicitly" hidden by a non-inverted filter.
214 // Only interesting for nodes.
215 boolean explicitlyFiltered = false;
216
217 for (FilterInfo fi: filters) {
218 if (fi.isDelete) {
219 if (filtered && fi.match.match(primitive)) {
220 filtered = false;
221 }
222 } else {
223 if ((!filtered || (!explicitlyFiltered && !fi.isInverted)) && fi.match.match(primitive)) {
224 filtered = true;
225 if (!fi.isInverted) {
226 explicitlyFiltered = true;
227 }
228 }
229 }
230 }
231
232 if (primitive instanceof Node) {
233 if (filtered) {
234 // If there is a parent way, that is not hidden, we show the
235 // node anyway, unless there is no non-inverted filter that
236 // applies to the node directly.
237 if (explicitlyFiltered)
238 return FilterType.PASSIV;
239 else {
240 if (oneParentWayNotFiltered(primitive, hidden))
241 return FilterType.NOT_FILTERED;
242 else
243 return FilterType.PASSIV;
244 }
245 } else {
246 if (!primitive.isTagged() && allParentWaysFiltered(primitive, hidden))
247 // Technically not hidden by any filter, but we hide it anyway, if
248 // it is untagged and all parent ways are hidden.
249 return FilterType.PASSIV;
250 else
251 return FilterType.NOT_FILTERED;
252 }
253 } else if (primitive instanceof Way) {
254 if (filtered) {
255 if (explicitlyFiltered)
256 return FilterType.EXPLICIT;
257 else {
258 if (oneParentMultipolygonNotFiltered(primitive, hidden))
259 return FilterType.NOT_FILTERED;
260 else
261 return FilterType.PASSIV;
262 }
263 } else {
264 if (!primitive.isTagged() && allParentMultipolygonsFiltered(primitive, hidden))
265 return FilterType.EXPLICIT;
266 else
267 return FilterType.NOT_FILTERED;
268 }
269 } else {
270 if (filtered)
271 return explicitlyFiltered ? FilterType.EXPLICIT : FilterType.PASSIV;
272 else
273 return FilterType.NOT_FILTERED;
274 }
275
276 }
277
278 /**
279 * Check if primitive is hidden.
280 * The filter flags for all parent objects must be set correctly, when
281 * calling this method.
282 * @param primitive the primitive
283 * @return FilterType.NOT_FILTERED when primitive is not hidden;
284 * FilterType.EXPLICIT when primitive is hidden and there is a non-inverted
285 * filter that applies;
286 * FilterType.PASSIV when primitive is hidden and all filters that apply
287 * are inverted
288 */
289 public FilterType isHidden(OsmPrimitive primitive) {
290 return test(hiddenFilters, primitive, true);
291 }
292
293 /**
294 * Check if primitive is disabled.
295 * The filter flags for all parent objects must be set correctly, when
296 * calling this method.
297 * @param primitive the primitive
298 * @return FilterType.NOT_FILTERED when primitive is not disabled;
299 * FilterType.EXPLICIT when primitive is disabled and there is a non-inverted
300 * filter that applies;
301 * FilterType.PASSIV when primitive is disabled and all filters that apply
302 * are inverted
303 */
304 public FilterType isDisabled(OsmPrimitive primitive) {
305 return test(disabledFilters, primitive, false);
306 }
307
308}
Note: See TracBrowser for help on using the repository browser.