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

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

checkstyle: enable relevant whitespace checks and fix them

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