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

Last change on this file since 6735 was 6735, checked in by simon04, 10 years ago

fix #8337 - Improve validator warning if relation members do not match the match expression

  • Property svn:eol-style set to native
File size: 10.6 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.Arrays;
9import java.util.Collection;
10import java.util.HashMap;
11import java.util.HashSet;
12import java.util.LinkedList;
13import java.util.Set;
14
15import org.openstreetmap.josm.command.Command;
16import org.openstreetmap.josm.command.DeleteCommand;
17import org.openstreetmap.josm.data.osm.Node;
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.osm.Way;
22import org.openstreetmap.josm.data.validation.Severity;
23import org.openstreetmap.josm.data.validation.Test;
24import org.openstreetmap.josm.data.validation.TestError;
25import org.openstreetmap.josm.gui.preferences.map.TaggingPresetPreference;
26import org.openstreetmap.josm.gui.tagging.TaggingPreset;
27import org.openstreetmap.josm.gui.tagging.TaggingPresetItem;
28import org.openstreetmap.josm.gui.tagging.TaggingPresetItems.Role;
29import org.openstreetmap.josm.gui.tagging.TaggingPresetItems.Key;
30import org.openstreetmap.josm.gui.tagging.TaggingPresetItems.Roles;
31import org.openstreetmap.josm.gui.tagging.TaggingPresetType;
32
33/**
34 * Check for wrong relations
35 *
36 */
37public class RelationChecker extends Test {
38
39 protected static final int ROLE_UNKNOWN = 1701;
40 protected static final int ROLE_EMPTY = 1702;
41 protected static final int WRONG_TYPE = 1703;
42 protected static final int HIGH_COUNT = 1704;
43 protected static final int LOW_COUNT = 1705;
44 protected static final int ROLE_MISSING = 1706;
45 protected static final int RELATION_UNKNOWN = 1707;
46 protected static final int RELATION_EMPTY = 1708;
47
48 /**
49 * Error message used to group errors related to role problems.
50 * @since 6731
51 */
52 public static final String ROLE_VERIF_PROBLEM_MSG = tr("Role verification problem");
53
54 /**
55 * Constructor
56 */
57 public RelationChecker() {
58 super(tr("Relation checker"),
59 tr("This plugin checks for errors in relations."));
60 }
61
62 @Override
63 public void initialize() {
64 initializePresets();
65 }
66
67 private static Collection<TaggingPreset> relationpresets = new LinkedList<TaggingPreset>();
68
69 /**
70 * Reads the presets data.
71 *
72 */
73 public void initializePresets() {
74 Collection<TaggingPreset> presets = TaggingPresetPreference.taggingPresets;
75 if (presets != null) {
76 for (TaggingPreset p : presets) {
77 for (TaggingPresetItem i : p.data) {
78 if (i instanceof Roles) {
79 relationpresets.add(p);
80 break;
81 }
82 }
83 }
84 }
85 }
86
87 private static class RoleInfo {
88 private int total = 0;
89 private Collection<Node> nodes = new LinkedList<Node>();
90 private Collection<Way> ways = new LinkedList<Way>();
91 private Collection<Way> openways = new LinkedList<Way>();
92 private Collection<Relation> relations = new LinkedList<Relation>();
93 }
94
95 @SuppressWarnings("unchecked")
96 @Override
97 public void visit(Relation n) {
98 LinkedList<Role> allroles = new LinkedList<Role>();
99 for (TaggingPreset p : relationpresets) {
100 boolean matches = true;
101 Roles r = null;
102 for (TaggingPresetItem i : p.data) {
103 if (i instanceof Key) {
104 Key k = (Key) i;
105 if (!k.value.equals(n.get(k.key))) {
106 matches = false;
107 break;
108 }
109 } else if (i instanceof Roles) {
110 r = (Roles) i;
111 }
112 }
113 if (matches && r != null) {
114 allroles.addAll(r.roles);
115 }
116 }
117 if (allroles.isEmpty()) {
118 errors.add( new TestError(this, Severity.WARNING, tr("Relation type is unknown"),
119 RELATION_UNKNOWN, n) );
120 } else {
121 HashMap<String,RoleInfo> map = new HashMap<String, RoleInfo>();
122 for (RelationMember m : n.getMembers()) {
123 String s = "";
124 if (m.hasRole()) {
125 s = m.getRole();
126 }
127 RoleInfo ri = map.get(s);
128 if (ri == null) {
129 ri = new RoleInfo();
130 }
131 ri.total++;
132 if (m.isRelation()) {
133 ri.relations.add(m.getRelation());
134 } else if(m.isWay()) {
135 ri.ways.add(m.getWay());
136 if (!m.getWay().isClosed()) {
137 ri.openways.add(m.getWay());
138 }
139 }
140 else if (m.isNode()) {
141 ri.nodes.add(m.getNode());
142 }
143 map.put(s, ri);
144 }
145 if(map.isEmpty()) {
146 errors.add( new TestError(this, Severity.ERROR, tr("Relation is empty"),
147 RELATION_EMPTY, n) );
148 } else {
149 LinkedList<String> done = new LinkedList<String>();
150 for (Role r : allroles) {
151 done.add(r.key);
152 String keyname = r.key;
153 if ("".equals(keyname)) {
154 keyname = tr("<empty>");
155 }
156 RoleInfo ri = map.get(r.key);
157 long count = (ri == null) ? 0 : ri.total;
158 long vc = r.getValidCount(count);
159 if (count != vc) {
160 if (count == 0) {
161 String s = marktr("Role {0} missing");
162 errors.add(new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG,
163 tr(s, keyname), MessageFormat.format(s, keyname), ROLE_MISSING, n));
164 }
165 else if (vc > count) {
166 String s = marktr("Number of {0} roles too low ({1})");
167 errors.add(new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG,
168 tr(s, keyname, count), MessageFormat.format(s, keyname, count), LOW_COUNT, n));
169 } else {
170 String s = marktr("Number of {0} roles too high ({1})");
171 errors.add(new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG,
172 tr(s, keyname, count), MessageFormat.format(s, keyname, count), HIGH_COUNT, n));
173 }
174 }
175 if (ri != null) {
176 if (r.types != null) {
177 Set<OsmPrimitive> wrongTypes = new HashSet<OsmPrimitive>();
178 if (!r.types.contains(TaggingPresetType.WAY)) {
179 wrongTypes.addAll(r.types.contains(TaggingPresetType.CLOSEDWAY) ? ri.openways : ri.ways);
180 }
181 if (!r.types.contains(TaggingPresetType.NODE)) {
182 wrongTypes.addAll(ri.nodes);
183 }
184 if (!r.types.contains(TaggingPresetType.RELATION)) {
185 wrongTypes.addAll(ri.relations);
186 }
187 if (!wrongTypes.isEmpty()) {
188 String s = marktr("Member for role {0} of wrong type");
189 LinkedList<OsmPrimitive> highlight = new LinkedList<OsmPrimitive>(wrongTypes);
190 highlight.addFirst(n);
191 errors.add(new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG,
192 tr(s, keyname), MessageFormat.format(s, keyname), WRONG_TYPE,
193 highlight, wrongTypes));
194 }
195 }
196 if (r.memberExpression != null) {
197 Set<OsmPrimitive> notMatching = new HashSet<OsmPrimitive>();
198 for (Collection<OsmPrimitive> c : Arrays.asList(new Collection[]{ri.nodes, ri.ways, ri.relations})) {
199 for (OsmPrimitive p : c) {
200 if (p.isUsable() && !r.memberExpression.match(p)) {
201 notMatching.add(p);
202 }
203 }
204 }
205 if (!notMatching.isEmpty()) {
206 String s = marktr("Member for role ''{0}'' does not match ''{1}''");
207 LinkedList<OsmPrimitive> highlight = new LinkedList<OsmPrimitive>(notMatching);
208 highlight.addFirst(n);
209 errors.add(new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG,
210 tr(s, keyname, r.memberExpression), MessageFormat.format(s, keyname, r.memberExpression), WRONG_TYPE,
211 highlight, notMatching));
212 }
213 }
214 }
215 }
216 for (String key : map.keySet()) {
217 if (!done.contains(key)) {
218 if (key.length() > 0) {
219 String s = marktr("Role {0} unknown");
220 errors.add(new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG,
221 tr(s, key), MessageFormat.format(s, key), ROLE_UNKNOWN, n));
222 } else {
223 String s = marktr("Empty role found");
224 errors.add(new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG,
225 tr(s), s, ROLE_EMPTY, n));
226 }
227 }
228 }
229 }
230 }
231 }
232
233 @Override
234 public Command fixError(TestError testError) {
235 if (isFixable(testError)) {
236 return new DeleteCommand(testError.getPrimitives());
237 }
238 return null;
239 }
240
241 @Override
242 public boolean isFixable(TestError testError) {
243 Collection<? extends OsmPrimitive> primitives = testError.getPrimitives();
244 return testError.getCode() == RELATION_EMPTY && !primitives.isEmpty() && primitives.iterator().next().isNew();
245 }
246}
Note: See TracBrowser for help on using the repository browser.