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

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

see #9844 - code cleanup

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