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

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

see #11390 - use method reference

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