Ticket #9844: RelationChecker.java-1.2.patch
File RelationChecker.java-1.2.patch, 17.8 KB (added by , 11 years ago) |
---|
-
data/defaultpresets.xml
6853 6853 </optional> 6854 6854 <roles> 6855 6855 <role key="from" text="from way" requisite="required" count="1" type="way" /> 6856 <role key="via" text="via node or ways" requisite="required" type="way,node" />6856 <role key="via" text="via node or ways" requisite="required" count="1" type="way,node" /> 6857 6857 <role key="to" text="to way" requisite="required" count="1" type="way" /> 6858 6858 </roles> 6859 6859 </item> -
src/org/openstreetmap/josm/data/validation/tests/RelationChecker.java
5 5 import static org.openstreetmap.josm.tools.I18n.tr; 6 6 7 7 import java.text.MessageFormat; 8 import java.util.ArrayList;9 8 import java.util.Collection; 9 import java.util.EnumSet; 10 10 import java.util.HashMap; 11 import java.util.HashSet;12 11 import java.util.LinkedList; 13 import java.util.List;14 import java.util.Set;15 12 16 13 import org.openstreetmap.josm.command.Command; 17 14 import org.openstreetmap.josm.command.DeleteCommand; 18 import org.openstreetmap.josm.data.osm.Node;19 15 import org.openstreetmap.josm.data.osm.OsmPrimitive; 16 import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 20 17 import org.openstreetmap.josm.data.osm.Relation; 21 18 import org.openstreetmap.josm.data.osm.RelationMember; 22 import org.openstreetmap.josm.data.osm.Way;23 19 import org.openstreetmap.josm.data.validation.Severity; 24 20 import org.openstreetmap.josm.data.validation.Test; 25 21 import org.openstreetmap.josm.data.validation.TestError; … … 30 26 import org.openstreetmap.josm.gui.tagging.TaggingPresetItems.Roles; 31 27 import org.openstreetmap.josm.gui.tagging.TaggingPresetType; 32 28 import org.openstreetmap.josm.gui.tagging.TaggingPresets; 29 import org.openstreetmap.josm.tools.Utils; 33 30 34 31 /** 35 32 * Check for wrong relations. … … 85 82 } 86 83 } 87 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 LinkedList<Role> roles; 91 private String name; 92 } 93 88 94 private static class RoleInfo { 89 95 private int total = 0; 90 private Collection<Node> nodes = new LinkedList<>();91 private Collection<Way> ways = new LinkedList<>();92 private Collection<Way> openways = new LinkedList<>();93 private Collection<Relation> relations = new LinkedList<>();94 96 } 95 97 96 98 @Override 97 99 public void visit(Relation n) { 98 LinkedList<Role> allroles = buildAllRoles(n);100 HashMap<String, RolePreset> allroles = buildAllRoles(n); 99 101 if (allroles.isEmpty() && n.hasTag("type", "route") 100 102 && n.hasTag("route", "train", "subway", "monorail", "tram", "bus", "trolleybus", "aerialway", "ferry")) { 101 103 errors.add(new TestError(this, Severity.WARNING, … … 120 122 RoleInfo ri = map.get(role); 121 123 if (ri == null) { 122 124 ri = new RoleInfo(); 125 map.put(role, ri); 123 126 } 124 127 ri.total++; 125 if (m.isRelation()) {126 ri.relations.add(m.getRelation());127 } else if(m.isWay()) {128 ri.ways.add(m.getWay());129 if (!m.getWay().isClosed()) {130 ri.openways.add(m.getWay());131 }132 }133 else if (m.isNode()) {134 ri.nodes.add(m.getNode());135 }136 map.put(role, ri);137 128 } 138 129 return map; 139 130 } 140 131 141 private LinkedList<Role> buildAllRoles(Relation n) { 142 LinkedList<Role> allroles = new LinkedList<>(); 132 // return Roles grouped by key 133 private HashMap<String, RolePreset> buildAllRoles(Relation n) { 134 HashMap<String, RolePreset> allroles = new HashMap<>(); 135 143 136 for (TaggingPreset p : relationpresets) { 144 137 boolean matches = true; 145 138 Roles r = null; … … 155 148 } 156 149 } 157 150 if (matches && r != null) { 158 allroles.addAll(r.roles); 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 } 159 162 } 160 163 } 161 164 return allroles; 162 165 } 163 166 164 private void checkRoles(Relation n, LinkedList<Role> allroles, HashMap<String, RoleInfo> map) { 165 List<String> done = new LinkedList<>(); 166 // Remove empty roles if several exist (like in route=hiking, see #9844) 167 List<Role> emptyRoles = new LinkedList<>(); 168 for (Role r : allroles) { 169 if ("".equals(r.key)) { 170 emptyRoles.add(r); 167 private boolean checkMemberType(Role r, RelationMember member) { 168 if (r.types != null) { 169 if (member.getDisplayType().equals(OsmPrimitiveType.NODE)) { 170 return r.types.contains(TaggingPresetType.NODE); 171 171 } 172 } 173 if (emptyRoles.size() > 1) { 174 allroles.removeAll(emptyRoles); 175 } 176 for (Role r : allroles) { 177 done.add(r.key); 178 String keyname = r.key; 179 if ("".equals(keyname)) { 180 keyname = tr("<empty>"); 172 if (member.getDisplayType().equals(OsmPrimitiveType.CLOSEDWAY)) { 173 return r.types.contains(TaggingPresetType.CLOSEDWAY); 181 174 } 182 RoleInfo ri = map.get(r.key); 183 checkRoleCounts(n, r, keyname, ri); 184 if (ri != null) { 185 if (r.types != null) { 186 checkRoleTypes(n, r, keyname, ri); 187 } 188 if (r.memberExpression != null) { 189 checkRoleMemberExpressions(n, r, keyname, ri); 190 } 175 if (member.getDisplayType().equals(OsmPrimitiveType.WAY)) { 176 return r.types.contains(TaggingPresetType.WAY); 177 } 178 if (member.getDisplayType().equals(OsmPrimitiveType.RELATION)) { 179 return r.types.contains(TaggingPresetType.RELATION); 191 180 } 181 // not matching type 182 return false; 183 } else { 184 // if no types specified, then test is passed 185 return true; 192 186 } 193 for (String key : map.keySet()) { 194 if (!done.contains(key)) { 195 if (key.length() > 0) { 196 String s = marktr("Role {0} unknown"); 197 errors.add(new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG, 198 tr(s, key), MessageFormat.format(s, key), ROLE_UNKNOWN, n)); 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; 199 213 } else { 200 String s = marktr("Empty role found"); 201 errors.add(new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG, 202 tr(s), s, ROLE_EMPTY, n)); 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 } 203 236 } 204 237 } 205 238 } 206 }207 239 208 private void checkRoleMemberExpressions(Relation n, Role r, String keyname, RoleInfo ri) { 209 Set<OsmPrimitive> notMatching = new HashSet<>(); 210 Collection<OsmPrimitive> allPrimitives = new ArrayList<>(); 211 allPrimitives.addAll(ri.nodes); 212 allPrimitives.addAll(ri.ways); 213 allPrimitives.addAll(ri.relations); 214 for (OsmPrimitive p : allPrimitives) { 215 if (p.isUsable() && !r.memberExpression.match(p)) { 216 notMatching.add(p); 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 EnumSet<TaggingPresetType> types = EnumSet.noneOf(TaggingPresetType.class); 252 for (Role r: rolePreset.roles) { 253 types.addAll(r.types); 217 254 } 218 } 219 if (!notMatching.isEmpty()) { 220 String s = marktr("Member for role ''{0}'' does not match ''{1}''"); 221 LinkedList<OsmPrimitive> highlight = new LinkedList<>(notMatching); 222 highlight.addFirst(n); 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 223 263 errors.add(new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG, 224 tr(s, keyname, r.memberExpression), MessageFormat.format(s, keyname, r.memberExpression), WRONG_TYPE,225 highlight, notMatching));264 tr(s, member.getType(), typesStr, rolePreset.name), s, WRONG_TYPE, 265 member.getMember().isUsable() ? member.getMember() : n)); 226 266 } 267 return false; 227 268 } 228 269 229 private void checkRoleTypes(Relation n, Role r, String keyname, RoleInfo ri) { 230 Set<OsmPrimitive> wrongTypes = new HashSet<>(); 231 if (!r.types.contains(TaggingPresetType.WAY)) { 232 wrongTypes.addAll(r.types.contains(TaggingPresetType.CLOSEDWAY) ? ri.openways : ri.ways); 233 } 234 if (!r.types.contains(TaggingPresetType.NODE)) { 235 wrongTypes.addAll(ri.nodes); 270 271 /** 272 * 273 * @param n relation to validate 274 * @param allroles contains presets for specified relation 275 * @param map contains statistics of occurances of specified role types in relation 276 */ 277 private void checkRoles(Relation n, HashMap<String, RolePreset> allroles, HashMap<String, RoleInfo> map) { 278 // go through all members of relation 279 for (RelationMember member: n.getMembers()) { 280 String role = member.getRole(); 281 282 283 // error reporting done inside 284 checkMemberExpressionAndType(allroles.get(role), member, n); 285 236 286 } 237 if (!r.types.contains(TaggingPresetType.RELATION)) { 238 wrongTypes.addAll(ri.relations); 287 288 // verify role counts based on whole role sets 289 for(RolePreset rp: allroles.values()) { 290 for (Role r: rp.roles) { 291 String keyname = r.key; 292 if ("".equals(keyname)) { 293 keyname = tr("<empty>"); 294 } 295 checkRoleCounts(n, r, keyname, map.get(r.key)); 296 } 239 297 } 240 if (!wrongTypes.isEmpty()) { 241 String s = marktr("Member for role {0} of wrong type"); 242 LinkedList<OsmPrimitive> highlight = new LinkedList<>(wrongTypes); 243 highlight.addFirst(n); 244 errors.add(new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG, 245 tr(s, keyname), MessageFormat.format(s, keyname), WRONG_TYPE, 246 highlight, wrongTypes)); 298 // verify unwanted members 299 for (String key : map.keySet()) { 300 if (!allroles.containsKey(key)) { 301 String templates = Utils.join("/", Utils.transform(allroles.keySet(), new Utils.Function<String, Object>() { 302 public Object apply(String x) { 303 return tr(x); 304 } 305 })); 306 307 if (key.length() > 0) { 308 String s = marktr("Role {0} unknown in templates {1}"); 309 310 errors.add(new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG, 311 tr(s, key, templates.toString()), MessageFormat.format(s, key), ROLE_UNKNOWN, n)); 312 } else { 313 String s = marktr("Empty role type found when expecting one of {0}"); 314 errors.add(new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG, 315 tr(s, templates), s, ROLE_EMPTY, n)); 316 } 317 } 247 318 } 248 319 } 249 320 321 250 322 private void checkRoleCounts(Relation n, Role r, String keyname, RoleInfo ri) { 251 323 long count = (ri == null) ? 0 : ri.total; 252 324 long vc = r.getValidCount(count); -
test/unit/org/openstreetmap/josm/data/validation/tests/RelationCheckerTest.groovy
1 1 // License: GPL. For details, see LICENSE file. 2 2 package org.openstreetmap.josm.data.validation.tests 3 3 4 import static org.openstreetmap.josm.TestUtils.createPrimitive 5 4 6 import org.openstreetmap.josm.JOSMFixture 5 7 import org.openstreetmap.josm.data.osm.Relation 6 8 import org.openstreetmap.josm.data.osm.RelationMember … … 8 10 import org.openstreetmap.josm.data.validation.TestError 9 11 import org.openstreetmap.josm.gui.tagging.TaggingPresets 10 12 11 import static org.openstreetmap.josm.TestUtils.createPrimitive12 13 13 class RelationCheckerTest extends GroovyTestCase { 14 14 15 15 @Override … … 54 54 55 55 def errors = testRelation(r) 56 56 assert errors.size() == 1 57 assert errors.get(0).getDescription() == "Role outer2 unknown "57 assert errors.get(0).getDescription() == "Role outer2 unknown in templates outer/inner" 58 58 } 59 59 60 60 void testRestrictionViaMissing() { … … 75 75 76 76 def errors = testRelation(r) 77 77 assert errors.size() == 1 78 assert errors.get(0).getDescription() == " Member for role via of wrong type"78 assert errors.get(0).getDescription() == "Role member type relation does not match accepted list of node/way in template Turn restriction" 79 79 } 80 80 81 81 void testRestrictionTwoFrom() { … … 99 99 100 100 def errors = testRelation(r) 101 101 assert errors.size() == 1 102 assert errors.get(0).getDescription() == "Empty role found"102 assert errors.get(0).getDescription().startsWith("Empty role type found when expecting one of") 103 103 } 104 104 105 105 void testPowerMemberExpression() { … … 108 108 109 109 def errors = testRelation(r) 110 110 assert errors.size() == 1 111 assert errors.get(0).getDescription() == " Member for role '<empty>' does not match 'power'"111 assert errors.get(0).getDescription() == "Role member does not match expression power in template Power route" 112 112 } 113 113 }