001// License: GPL. See LICENSE file for details. 002package org.openstreetmap.josm.data.validation.tests; 003 004import static org.openstreetmap.josm.tools.I18n.marktr; 005import static org.openstreetmap.josm.tools.I18n.tr; 006 007import java.text.MessageFormat; 008import java.util.Arrays; 009import java.util.Collection; 010import java.util.HashMap; 011import java.util.HashSet; 012import java.util.LinkedList; 013import java.util.Set; 014 015import org.openstreetmap.josm.command.Command; 016import org.openstreetmap.josm.command.DeleteCommand; 017import org.openstreetmap.josm.data.osm.Node; 018import org.openstreetmap.josm.data.osm.OsmPrimitive; 019import org.openstreetmap.josm.data.osm.Relation; 020import org.openstreetmap.josm.data.osm.RelationMember; 021import org.openstreetmap.josm.data.osm.Way; 022import org.openstreetmap.josm.data.validation.Severity; 023import org.openstreetmap.josm.data.validation.Test; 024import org.openstreetmap.josm.data.validation.TestError; 025import org.openstreetmap.josm.gui.preferences.map.TaggingPresetPreference; 026import org.openstreetmap.josm.gui.tagging.TaggingPreset; 027import org.openstreetmap.josm.gui.tagging.TaggingPresetItem; 028import org.openstreetmap.josm.gui.tagging.TaggingPresetItems.Role; 029import org.openstreetmap.josm.gui.tagging.TaggingPresetItems.Key; 030import org.openstreetmap.josm.gui.tagging.TaggingPresetItems.Roles; 031import org.openstreetmap.josm.gui.tagging.TaggingPresetType; 032 033/** 034 * Check for wrong relations 035 * 036 */ 037public class RelationChecker extends Test { 038 039 protected static final int ROLE_UNKNOWN = 1701; 040 protected static final int ROLE_EMPTY = 1702; 041 protected static final int WRONG_TYPE = 1703; 042 protected static final int HIGH_COUNT = 1704; 043 protected static final int LOW_COUNT = 1705; 044 protected static final int ROLE_MISSING = 1706; 045 protected static final int RELATION_UNKNOWN = 1707; 046 protected static final int RELATION_EMPTY = 1708; 047 048 /** 049 * Constructor 050 */ 051 public RelationChecker() { 052 super(tr("Relation checker"), 053 tr("This plugin checks for errors in relations.")); 054 } 055 056 @Override 057 public void initialize() { 058 initializePresets(); 059 } 060 061 private static Collection<TaggingPreset> relationpresets = new LinkedList<TaggingPreset>(); 062 063 /** 064 * Reads the presets data. 065 * 066 */ 067 public void initializePresets() { 068 Collection<TaggingPreset> presets = TaggingPresetPreference.taggingPresets; 069 if (presets != null) { 070 for (TaggingPreset p : presets) { 071 for (TaggingPresetItem i : p.data) { 072 if (i instanceof Roles) { 073 relationpresets.add(p); 074 break; 075 } 076 } 077 } 078 } 079 } 080 081 private static class RoleInfo { 082 private int total = 0; 083 private Collection<Node> nodes = new LinkedList<Node>(); 084 private Collection<Way> ways = new LinkedList<Way>(); 085 private Collection<Way> openways = new LinkedList<Way>(); 086 private Collection<Relation> relations = new LinkedList<Relation>(); 087 } 088 089 @SuppressWarnings("unchecked") 090 @Override 091 public void visit(Relation n) { 092 LinkedList<Role> allroles = new LinkedList<Role>(); 093 for (TaggingPreset p : relationpresets) { 094 boolean matches = true; 095 Roles r = null; 096 for (TaggingPresetItem i : p.data) { 097 if (i instanceof Key) { 098 Key k = (Key) i; 099 if (!k.value.equals(n.get(k.key))) { 100 matches = false; 101 break; 102 } 103 } else if (i instanceof Roles) { 104 r = (Roles) i; 105 } 106 } 107 if (matches && r != null) { 108 allroles.addAll(r.roles); 109 } 110 } 111 if (allroles.isEmpty()) { 112 errors.add( new TestError(this, Severity.WARNING, tr("Relation type is unknown"), 113 RELATION_UNKNOWN, n) ); 114 } else { 115 HashMap<String,RoleInfo> map = new HashMap<String, RoleInfo>(); 116 for (RelationMember m : n.getMembers()) { 117 String s = ""; 118 if (m.hasRole()) { 119 s = m.getRole(); 120 } 121 RoleInfo ri = map.get(s); 122 if (ri == null) { 123 ri = new RoleInfo(); 124 } 125 ri.total++; 126 if (m.isRelation()) { 127 ri.relations.add(m.getRelation()); 128 } else if(m.isWay()) { 129 ri.ways.add(m.getWay()); 130 if (!m.getWay().isClosed()) { 131 ri.openways.add(m.getWay()); 132 } 133 } 134 else if (m.isNode()) { 135 ri.nodes.add(m.getNode()); 136 } 137 map.put(s, ri); 138 } 139 if(map.isEmpty()) { 140 errors.add( new TestError(this, Severity.ERROR, tr("Relation is empty"), 141 RELATION_EMPTY, n) ); 142 } else { 143 LinkedList<String> done = new LinkedList<String>(); 144 String errorMessage = tr("Role verification problem"); 145 for (Role r : allroles) { 146 done.add(r.key); 147 String keyname = r.key; 148 if ("".equals(keyname)) { 149 keyname = tr("<empty>"); 150 } 151 RoleInfo ri = map.get(r.key); 152 long count = (ri == null) ? 0 : ri.total; 153 long vc = r.getValidCount(count); 154 if (count != vc) { 155 if (count == 0) { 156 String s = marktr("Role {0} missing"); 157 errors.add(new TestError(this, Severity.WARNING, errorMessage, 158 tr(s, keyname), MessageFormat.format(s, keyname), ROLE_MISSING, n)); 159 } 160 else if (vc > count) { 161 String s = marktr("Number of {0} roles too low ({1})"); 162 errors.add(new TestError(this, Severity.WARNING, errorMessage, 163 tr(s, keyname, count), MessageFormat.format(s, keyname, count), LOW_COUNT, n)); 164 } else { 165 String s = marktr("Number of {0} roles too high ({1})"); 166 errors.add(new TestError(this, Severity.WARNING, errorMessage, 167 tr(s, keyname, count), MessageFormat.format(s, keyname, count), HIGH_COUNT, n)); 168 } 169 } 170 if (ri != null) { 171 Set<OsmPrimitive> wrongTypes = new HashSet<OsmPrimitive>(); 172 if (r.types != null) { 173 if (!r.types.contains(TaggingPresetType.WAY)) { 174 wrongTypes.addAll(r.types.contains(TaggingPresetType.CLOSEDWAY) ? ri.openways : ri.ways); 175 } 176 if (!r.types.contains(TaggingPresetType.NODE)) { 177 wrongTypes.addAll(ri.nodes); 178 } 179 if (!r.types.contains(TaggingPresetType.RELATION)) { 180 wrongTypes.addAll(ri.relations); 181 } 182 } 183 if (r.memberExpression != null) { 184 for (Collection<OsmPrimitive> c : Arrays.asList(new Collection[]{ri.nodes, ri.ways, ri.relations})) { 185 for (OsmPrimitive p : c) { 186 if (p.isUsable() && !r.memberExpression.match(p)) { 187 wrongTypes.add(p); 188 } 189 } 190 } 191 } 192 if (!wrongTypes.isEmpty()) { 193 String s = marktr("Member for role {0} of wrong type"); 194 LinkedList<OsmPrimitive> highlight = new LinkedList<OsmPrimitive>(wrongTypes); 195 highlight.addFirst(n); 196 errors.add(new TestError(this, Severity.WARNING, errorMessage, 197 tr(s, keyname), MessageFormat.format(s, keyname), WRONG_TYPE, 198 highlight, wrongTypes)); 199 } 200 } 201 } 202 for (String key : map.keySet()) { 203 if (!done.contains(key)) { 204 if (key.length() > 0) { 205 String s = marktr("Role {0} unknown"); 206 errors.add(new TestError(this, Severity.WARNING, errorMessage, 207 tr(s, key), MessageFormat.format(s, key), ROLE_UNKNOWN, n)); 208 } else { 209 String s = marktr("Empty role found"); 210 errors.add(new TestError(this, Severity.WARNING, errorMessage, 211 tr(s), s, ROLE_EMPTY, n)); 212 } 213 } 214 } 215 } 216 } 217 } 218 219 @Override 220 public Command fixError(TestError testError) { 221 if (isFixable(testError)) { 222 return new DeleteCommand(testError.getPrimitives()); 223 } 224 return null; 225 } 226 227 @Override 228 public boolean isFixable(TestError testError) { 229 Collection<? extends OsmPrimitive> primitives = testError.getPrimitives(); 230 return testError.getCode() == RELATION_EMPTY && !primitives.isEmpty() && primitives.iterator().next().isNew(); 231 } 232}