source: josm/trunk/src/org/openstreetmap/josm/data/validation/tests/RelationChecker.java@ 12006

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

see #14692 - checkstyle

  • Property svn:eol-style set to native
File size: 14.4 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.validation.tests;
3
4import static org.openstreetmap.josm.tools.I18n.marktr;
5import static org.openstreetmap.josm.tools.I18n.tr;
6
7import java.util.Collection;
8import java.util.EnumSet;
9import java.util.HashMap;
10import java.util.LinkedHashMap;
11import java.util.LinkedList;
12import java.util.Map;
13import java.util.stream.Collectors;
14
15import org.openstreetmap.josm.command.Command;
16import org.openstreetmap.josm.command.DeleteCommand;
17import org.openstreetmap.josm.data.osm.OsmPrimitive;
18import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
19import org.openstreetmap.josm.data.osm.Relation;
20import org.openstreetmap.josm.data.osm.RelationMember;
21import org.openstreetmap.josm.data.validation.Severity;
22import org.openstreetmap.josm.data.validation.Test;
23import org.openstreetmap.josm.data.validation.TestError;
24import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
25import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem;
26import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType;
27import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
28import org.openstreetmap.josm.gui.tagging.presets.items.KeyedItem;
29import org.openstreetmap.josm.gui.tagging.presets.items.Roles;
30import org.openstreetmap.josm.gui.tagging.presets.items.Roles.Role;
31import org.openstreetmap.josm.tools.Utils;
32
33/**
34 * Check for wrong relations.
35 * @since 3669
36 */
37public class RelationChecker extends Test {
38
39 // CHECKSTYLE.OFF: SingleSpaceSeparator
40 /** Role {0} unknown in templates {1} */
41 public static final int ROLE_UNKNOWN = 1701;
42 /** Empty role type found when expecting one of {0} */
43 public static final int ROLE_EMPTY = 1702;
44 /** Role member does not match expression {0} in template {1} */
45 public static final int WRONG_TYPE = 1703;
46 /** Number of {0} roles too high ({1}) */
47 public static final int HIGH_COUNT = 1704;
48 /** Number of {0} roles too low ({1}) */
49 public static final int LOW_COUNT = 1705;
50 /** Role {0} missing */
51 public static final int ROLE_MISSING = 1706;
52 /** Relation type is unknown */
53 public static final int RELATION_UNKNOWN = 1707;
54 /** Relation is empty */
55 public static final int RELATION_EMPTY = 1708;
56 // CHECKSTYLE.ON: SingleSpaceSeparator
57
58 /**
59 * Error message used to group errors related to role problems.
60 * @since 6731
61 */
62 public static final String ROLE_VERIF_PROBLEM_MSG = tr("Role verification problem");
63
64 /**
65 * Constructor
66 */
67 public RelationChecker() {
68 super(tr("Relation checker"),
69 tr("Checks for errors in relations."));
70 }
71
72 @Override
73 public void initialize() {
74 initializePresets();
75 }
76
77 private static final Collection<TaggingPreset> relationpresets = new LinkedList<>();
78
79 /**
80 * Reads the presets data.
81 */
82 public static synchronized void initializePresets() {
83 if (!relationpresets.isEmpty()) {
84 // the presets have already been initialized
85 return;
86 }
87 for (TaggingPreset p : TaggingPresets.getTaggingPresets()) {
88 for (TaggingPresetItem i : p.data) {
89 if (i instanceof Roles) {
90 relationpresets.add(p);
91 break;
92 }
93 }
94 }
95 }
96
97 private static class RoleInfo {
98 private int total;
99 }
100
101 @Override
102 public void visit(Relation n) {
103 Map<Role, String> allroles = buildAllRoles(n);
104 if (allroles.isEmpty() && n.hasTag("type", "route")
105 && n.hasTag("route", "train", "subway", "monorail", "tram", "bus", "trolleybus", "aerialway", "ferry")) {
106 errors.add(TestError.builder(this, Severity.WARNING, RELATION_UNKNOWN)
107 .message(tr("Route scheme is unspecified. Add {0} ({1}=public_transport; {2}=legacy)", "public_transport:version", "2", "1"))
108 .primitives(n)
109 .build());
110 } else if (allroles.isEmpty()) {
111 errors.add(TestError.builder(this, Severity.WARNING, RELATION_UNKNOWN)
112 .message(tr("Relation type is unknown"))
113 .primitives(n)
114 .build());
115 }
116
117 Map<String, RoleInfo> map = buildRoleInfoMap(n);
118 if (map.isEmpty()) {
119 errors.add(TestError.builder(this, Severity.ERROR, RELATION_EMPTY)
120 .message(tr("Relation is empty"))
121 .primitives(n)
122 .build());
123 } else if (!allroles.isEmpty()) {
124 checkRoles(n, allroles, map);
125 }
126 }
127
128 private static Map<String, RoleInfo> buildRoleInfoMap(Relation n) {
129 Map<String, RoleInfo> map = new HashMap<>();
130 for (RelationMember m : n.getMembers()) {
131 String role = m.getRole();
132 RoleInfo ri = map.get(role);
133 if (ri == null) {
134 ri = new RoleInfo();
135 map.put(role, ri);
136 }
137 ri.total++;
138 }
139 return map;
140 }
141
142 // return Roles grouped by key
143 private static Map<Role, String> buildAllRoles(Relation n) {
144 Map<Role, String> allroles = new LinkedHashMap<>();
145
146 for (TaggingPreset p : relationpresets) {
147 final boolean matches = TaggingPresetItem.matches(Utils.filteredCollection(p.data, KeyedItem.class), n.getKeys());
148 final Roles r = Utils.find(p.data, Roles.class);
149 if (matches && r != null) {
150 for (Role role: r.roles) {
151 allroles.put(role, p.name);
152 }
153 }
154 }
155 return allroles;
156 }
157
158 private boolean checkMemberType(Role r, RelationMember member) {
159 if (r.types != null) {
160 switch (member.getDisplayType()) {
161 case NODE:
162 return r.types.contains(TaggingPresetType.NODE);
163 case CLOSEDWAY:
164 return r.types.contains(TaggingPresetType.CLOSEDWAY);
165 case WAY:
166 return r.types.contains(TaggingPresetType.WAY);
167 case MULTIPOLYGON:
168 return r.types.contains(TaggingPresetType.MULTIPOLYGON);
169 case RELATION:
170 return r.types.contains(TaggingPresetType.RELATION);
171 default: // not matching type
172 return false;
173 }
174 } else {
175 // if no types specified, then test is passed
176 return true;
177 }
178 }
179
180 /**
181 * get all role definition for specified key and check, if some definition matches
182 *
183 * @param allroles containing list of possible role presets of the member
184 * @param member to be verified
185 * @param n relation to be verified
186 * @return <tt>true</tt> if member passed any of definition within preset
187 *
188 */
189 private boolean checkMemberExpressionAndType(Map<Role, String> allroles, RelationMember member, Relation n) {
190 String role = member.getRole();
191 String name = null;
192 // Set of all accepted types in template
193 Collection<TaggingPresetType> types = EnumSet.noneOf(TaggingPresetType.class);
194 TestError possibleMatchError = null;
195 // iterate through all of the role definition within preset
196 // and look for any matching definition
197 for (Map.Entry<Role, String> e : allroles.entrySet()) {
198 Role r = e.getKey();
199 if (!r.isRole(role)) {
200 continue;
201 }
202 name = e.getValue();
203 types.addAll(r.types);
204 if (checkMemberType(r, member)) {
205 // member type accepted by role definition
206 if (r.memberExpression == null) {
207 // no member expression - so all requirements met
208 return true;
209 } else {
210 // verify if preset accepts such member
211 OsmPrimitive primitive = member.getMember();
212 if (!primitive.isUsable()) {
213 // if member is not usable (i.e. not present in working set)
214 // we can't verify expression - so we just skip it
215 return true;
216 } else {
217 // verify expression
218 if (r.memberExpression.match(primitive)) {
219 return true;
220 } else {
221 // possible match error
222 // we still need to iterate further, as we might have
223 // different present, for which memberExpression will match
224 // but stash the error in case no better reason will be found later
225 possibleMatchError = TestError.builder(this, Severity.WARNING, WRONG_TYPE)
226 .message(ROLE_VERIF_PROBLEM_MSG,
227 marktr("Role of relation member does not match expression ''{0}'' in template {1}"),
228 r.memberExpression, name)
229 .primitives(member.getMember().isUsable() ? member.getMember() : n)
230 .build();
231 }
232 }
233 }
234 } else if (OsmPrimitiveType.RELATION.equals(member.getType()) && !member.getMember().isUsable()
235 && r.types.contains(TaggingPresetType.MULTIPOLYGON)) {
236 // if relation is incomplete we cannot verify if it's a multipolygon - so we just skip it
237 return true;
238 }
239 }
240
241 if (name == null) {
242 return true;
243 } else if (possibleMatchError != null) {
244 // if any error found, then assume that member type was correct
245 // and complain about not matching the memberExpression
246 // (the only failure, that we could gather)
247 errors.add(possibleMatchError);
248 } else {
249 // no errors found till now. So member at least failed at matching the type
250 // it could also fail at memberExpression, but we can't guess at which
251
252 // convert in localization friendly way to string of accepted types
253 String typesStr = types.stream().map(x -> tr(x.getName())).collect(Collectors.joining("/"));
254
255 errors.add(TestError.builder(this, Severity.WARNING, WRONG_TYPE)
256 .message(ROLE_VERIF_PROBLEM_MSG,
257 marktr("Type ''{0}'' of relation member with role ''{1}'' does not match accepted types ''{2}'' in template {3}"),
258 member.getType(), member.getRole(), typesStr, name)
259 .primitives(member.getMember().isUsable() ? member.getMember() : n)
260 .build());
261 }
262 return false;
263 }
264
265 /**
266 *
267 * @param n relation to validate
268 * @param allroles contains presets for specified relation
269 * @param map contains statistics of occurances of specified role types in relation
270 */
271 private void checkRoles(Relation n, Map<Role, String> allroles, Map<String, RoleInfo> map) {
272 // go through all members of relation
273 for (RelationMember member: n.getMembers()) {
274 // error reporting done inside
275 checkMemberExpressionAndType(allroles, member, n);
276 }
277
278 // verify role counts based on whole role sets
279 for (Role r: allroles.keySet()) {
280 String keyname = r.key;
281 if (keyname.isEmpty()) {
282 keyname = tr("<empty>");
283 }
284 checkRoleCounts(n, r, keyname, map.get(r.key));
285 }
286 if ("network".equals(n.get("type")) && !"bicycle".equals(n.get("route"))) {
287 return;
288 }
289 // verify unwanted members
290 for (String key : map.keySet()) {
291 boolean found = false;
292 for (Role r: allroles.keySet()) {
293 if (r.isRole(key)) {
294 found = true;
295 break;
296 }
297 }
298
299 if (!found) {
300 String templates = allroles.keySet().stream().map(r -> r.key).collect(Collectors.joining("/"));
301
302 if (!key.isEmpty()) {
303 errors.add(TestError.builder(this, Severity.WARNING, ROLE_UNKNOWN)
304 .message(ROLE_VERIF_PROBLEM_MSG, marktr("Role ''{0}'' unknown in templates ''{1}''"), key, templates)
305 .primitives(n)
306 .build());
307 } else {
308 errors.add(TestError.builder(this, Severity.WARNING, ROLE_EMPTY)
309 .message(ROLE_VERIF_PROBLEM_MSG, marktr("Empty role type found when expecting one of ''{0}''"), templates)
310 .primitives(n)
311 .build());
312 }
313 }
314 }
315 }
316
317 private void checkRoleCounts(Relation n, Role r, String keyname, RoleInfo ri) {
318 long count = (ri == null) ? 0 : ri.total;
319 long vc = r.getValidCount(count);
320 if (count != vc) {
321 if (count == 0) {
322 errors.add(TestError.builder(this, Severity.WARNING, ROLE_MISSING)
323 .message(ROLE_VERIF_PROBLEM_MSG, marktr("Role ''{0}'' missing"), keyname)
324 .primitives(n)
325 .build());
326 } else if (vc > count) {
327 errors.add(TestError.builder(this, Severity.WARNING, LOW_COUNT)
328 .message(ROLE_VERIF_PROBLEM_MSG, marktr("Number of ''{0}'' roles too low ({1})"), keyname, count)
329 .primitives(n)
330 .build());
331 } else {
332 errors.add(TestError.builder(this, Severity.WARNING, HIGH_COUNT)
333 .message(ROLE_VERIF_PROBLEM_MSG, marktr("Number of ''{0}'' roles too high ({1})"), keyname, count)
334 .primitives(n)
335 .build());
336 }
337 }
338 }
339
340 @Override
341 public Command fixError(TestError testError) {
342 if (isFixable(testError) && !testError.getPrimitives().iterator().next().isDeleted()) {
343 return new DeleteCommand(testError.getPrimitives());
344 }
345 return null;
346 }
347
348 @Override
349 public boolean isFixable(TestError testError) {
350 Collection<? extends OsmPrimitive> primitives = testError.getPrimitives();
351 return testError.getCode() == RELATION_EMPTY && !primitives.isEmpty() && primitives.iterator().next().isNew();
352 }
353}
Note: See TracBrowser for help on using the repository browser.