001// License: GPL. For details, see LICENSE file. 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.util.Arrays; 008import java.util.Collections; 009import java.util.HashSet; 010import java.util.Set; 011 012import org.openstreetmap.josm.data.osm.OsmPrimitive; 013import org.openstreetmap.josm.data.osm.OsmUtils; 014import org.openstreetmap.josm.data.osm.Relation; 015import org.openstreetmap.josm.data.osm.Way; 016import org.openstreetmap.josm.data.validation.Severity; 017import org.openstreetmap.josm.data.validation.Test; 018import org.openstreetmap.josm.data.validation.TestError; 019import org.openstreetmap.josm.gui.mappaint.ElemStyles; 020 021/** 022 * Check area type ways for errors 023 * 024 * @author stoecker 025 * @since 3669 026 */ 027public class UnclosedWays extends Test { 028 029 /** 030 * Constructs a new {@code UnclosedWays} test. 031 */ 032 public UnclosedWays() { 033 super(tr("Unclosed Ways"), tr("This tests if ways which should be circular are closed.")); 034 } 035 036 /** 037 * A check performed by UnclosedWays test. 038 * @since 6390 039 */ 040 private static class UnclosedWaysCheck { 041 /** The unique numeric code for this check */ 042 public final int code; 043 /** The OSM key checked */ 044 public final String key; 045 /** The English message */ 046 private final String engMessage; 047 /** The special values, to be ignored if ignore is set to true; to be considered only if ignore is set to false */ 048 private final Set<String> specialValues; 049 /** The boolean indicating if special values must be ignored or considered only */ 050 private final boolean ignore; 051 052 /** 053 * Constructs a new {@code UnclosedWaysCheck}. 054 * @param code The unique numeric code for this check 055 * @param key The OSM key checked 056 * @param engMessage The English message 057 */ 058 UnclosedWaysCheck(int code, String key, String engMessage) { 059 this(code, key, engMessage, Collections.<String>emptySet()); 060 } 061 062 /** 063 * Constructs a new {@code UnclosedWaysCheck}. 064 * @param code The unique numeric code for this check 065 * @param key The OSM key checked 066 * @param engMessage The English message 067 * @param ignoredValues The ignored values. 068 */ 069 UnclosedWaysCheck(int code, String key, String engMessage, Set<String> ignoredValues) { 070 this(code, key, engMessage, ignoredValues, true); 071 } 072 073 /** 074 * Constructs a new {@code UnclosedWaysCheck}. 075 * @param code The unique numeric code for this check 076 * @param key The OSM key checked 077 * @param engMessage The English message 078 * @param specialValues The special values, to be ignored if ignore is set to true; to be considered only if ignore is set to false 079 * @param ignore indicates if special values must be ignored or considered only 080 */ 081 UnclosedWaysCheck(int code, String key, String engMessage, Set<String> specialValues, boolean ignore) { 082 this.code = code; 083 this.key = key; 084 this.engMessage = engMessage; 085 this.specialValues = specialValues; 086 this.ignore = ignore; 087 } 088 089 /** 090 * Returns the test error of the given way, if any. 091 * @param w The way to check 092 * @param test parent test 093 * @return The test error if the way is erroneous, {@code null} otherwise 094 */ 095 public final TestError getTestError(Way w, UnclosedWays test) { 096 String value = w.get(key); 097 if (isValueErroneous(value)) { 098 return TestError.builder(test, Severity.WARNING, code) 099 .message(tr("Unclosed way"), engMessage, engMessage.contains("{0}") ? new Object[]{value} : new Object[]{}) 100 .primitives(w) 101 .highlight(Arrays.asList(w.firstNode(), w.lastNode())) 102 .build(); 103 } 104 return null; 105 } 106 107 protected boolean isValueErroneous(String value) { 108 return value != null && ignore != specialValues.contains(value); 109 } 110 } 111 112 /** 113 * A check performed by UnclosedWays test where the key is treated as boolean. 114 * @since 6390 115 */ 116 private static final class UnclosedWaysBooleanCheck extends UnclosedWaysCheck { 117 118 /** 119 * Constructs a new {@code UnclosedWaysBooleanCheck}. 120 * @param code The unique numeric code for this check 121 * @param key The OSM key checked 122 * @param engMessage The English message 123 */ 124 UnclosedWaysBooleanCheck(int code, String key, String engMessage) { 125 super(code, key, engMessage); 126 } 127 128 @Override 129 protected boolean isValueErroneous(String value) { 130 Boolean btest = OsmUtils.getOsmBoolean(value); 131 // Not a strict boolean comparison to handle building=house like a building=yes 132 return (btest != null && btest) || (btest == null && value != null); 133 } 134 } 135 136 private static final UnclosedWaysCheck[] checks = { 137 // CHECKSTYLE.OFF: SingleSpaceSeparator 138 // list contains natural tag allowed on unclosed ways as well as those only allowed on nodes to avoid 139 // duplicate warnings 140 new UnclosedWaysCheck(1101, "natural", marktr("natural type {0}"), 141 new HashSet<>(Arrays.asList("arete", "bay", "cave", "cliff", "coastline", "gorge", "gully", "peak", 142 "ridge", "saddle", "strait", "tree", "tree_row", "valley", "volcano"))), 143 144 new UnclosedWaysCheck(1102, "landuse", marktr("landuse type {0}")), 145 new UnclosedWaysCheck(1103, "amenities", marktr("amenities type {0}")), 146 new UnclosedWaysCheck(1104, "sport", marktr("sport type {0}"), 147 new HashSet<>(Arrays.asList("water_slide", "climbing", "skiing", "toboggan", "bobsleigh"))), 148 new UnclosedWaysCheck(1105, "tourism", marktr("tourism type {0}"), 149 new HashSet<>(Arrays.asList("attraction", "artwork"))), 150 new UnclosedWaysCheck(1106, "shop", marktr("shop type {0}")), 151 new UnclosedWaysCheck(1107, "leisure", marktr("leisure type {0}"), 152 new HashSet<>(Arrays.asList("track", "slipway"))), 153 new UnclosedWaysCheck(1108, "waterway", marktr("waterway type {0}"), 154 new HashSet<>(Arrays.asList("riverbank")), false), 155 new UnclosedWaysCheck(1109, "boundary", marktr("boundary type {0}")), 156 new UnclosedWaysCheck(1110, "area:highway", marktr("area:highway type {0}")), 157 new UnclosedWaysBooleanCheck(1120, "building", marktr("building")), 158 new UnclosedWaysBooleanCheck(1130, "area", marktr("area")), 159 // 1131: Area style way is not closed 160 // CHECKSTYLE.ON: SingleSpaceSeparator 161 }; 162 163 /** 164 * Returns the set of checked OSM keys. 165 * @return The set of checked OSM keys. 166 * @since 6390 167 */ 168 public Set<String> getCheckedKeys() { 169 Set<String> keys = new HashSet<>(); 170 for (UnclosedWaysCheck c : checks) { 171 keys.add(c.key); 172 } 173 return keys; 174 } 175 176 @Override 177 public void visit(Way w) { 178 179 if (!w.isUsable() || w.isArea()) 180 return; 181 182 for (OsmPrimitive parent: w.getReferrers()) { 183 if (parent instanceof Relation && ((Relation) parent).isMultipolygon()) 184 return; 185 } 186 187 for (UnclosedWaysCheck c : checks) { 188 TestError error = c.getTestError(w, this); 189 if (error != null) { 190 errors.add(error); 191 return; 192 } 193 } 194 // code 1131: other area style ways 195 if (ElemStyles.hasOnlyAreaElements(w) && !w.getNodes().isEmpty()) { 196 errors.add(TestError.builder(this, Severity.WARNING, 1131) 197 .message(tr("Unclosed way"), marktr("Area style way is not closed"), new Object()) 198 .primitives(w) 199 .highlight(Arrays.asList(w.firstNode(), w.lastNode())) 200 .build()); 201 } 202 } 203}